xcactivitylog 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fc90141684fae80212880fb48cfb68fa36773c2d27428df4f1e2e7eccb89d988
4
+ data.tar.gz: 074f11361b4312d978ac19e14fb6f4c56fc958308866a92f63c0f4a2f4fddac0
5
+ SHA512:
6
+ metadata.gz: 450ac2460c8789d8efba482f009ff7813fe5e238b5f647bc6fe1bbf9ec249a364920aed7c2fce81a13c74c8c30d54407fdff2d981451bf78740b6c8f0bd90dc8
7
+ data.tar.gz: f5711987622f8ea0589f4b8249867d447923ebbe6b775384c3113eec42b39960618d2472efecf196e7b9b04f61c8422912b36429c414605139a8d485912cc114
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at segiddins@segiddins.me. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Samuel Giddins
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,45 @@
1
+ # xcactivitylog
2
+
3
+ Easy parse Xcode's `.xcactivitylog` files!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'xcactivitylog'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install xcactivitylog
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'xcactivitylog'
25
+
26
+ toplevel_sections = XCActivityLog.parse_file(path: 'path/to/log.xcactivitylog')
27
+ ```
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/segiddins/xcactivitylog. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
38
+
39
+ ## License
40
+
41
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
42
+
43
+ ## Code of Conduct
44
+
45
+ Everyone interacting in the Xcactivitylog project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/segiddins/xcactivitylog/blob/master/CODE_OF_CONDUCT.md).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SLF0
4
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slf0/token'
4
+ require 'slf0/tokenizer'
5
+
6
+ module SLF0
7
+ class Parser
8
+ def initialize(class_deserializer:)
9
+ @class_deserializer = class_deserializer
10
+ end
11
+
12
+ def parse!(io)
13
+ tokens = Tokenizer.tokenize(io)
14
+
15
+ parse_tokens!(tokens)
16
+ end
17
+
18
+ def parse_tokens!(tokens)
19
+ class_names = tokens.grep(SLF0::Token::ClassName).map(&:value)
20
+ tokens.reject! { |t| t.is_a? SLF0::Token::ClassName }
21
+ values = []
22
+ stream = make_stream(tokens, [nil] + class_names.map { |n| [n, @class_deserializer[n]] })
23
+ until tokens.empty?
24
+ values << case tokens.first
25
+ when SLF0::Token::ClassNameRef
26
+ stream.object
27
+ else
28
+ tokens.shift.value
29
+ end
30
+ end
31
+ values
32
+ end
33
+
34
+ def make_stream(tokens, class_deserializer)
35
+ SLF0::Token::Stream.new(tokens, class_deserializer: class_deserializer)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SLF0
4
+ class Token
5
+ attr_reader :value
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def to_s
11
+ value.to_s
12
+ end
13
+
14
+ def inspect
15
+ "#<#{self.class} #{value.inspect}>"
16
+ end
17
+
18
+ class ObjectListNil < Token
19
+ end
20
+ class Int < Token
21
+ alias int value
22
+ end
23
+ class ClassName < Token
24
+ end
25
+ class ClassNameRef < Token
26
+ end
27
+ class String < Token
28
+ alias string value
29
+ end
30
+ class Double < Token
31
+ alias double value
32
+ end
33
+ class ObjectList < Token
34
+ alias length value
35
+ end
36
+
37
+ class Stream
38
+ def initialize(tokens, class_deserializer:)
39
+ @tokens = tokens
40
+ @class_deserializer = class_deserializer
41
+ end
42
+
43
+ def int(reason = nil)
44
+ shift(Int, reason).value
45
+ end
46
+
47
+ def string(reason = nil)
48
+ return if shift_nil?(reason)
49
+
50
+ shift(String, reason).value
51
+ end
52
+
53
+ def double(reason = nil)
54
+ return if shift_nil?
55
+
56
+ shift(Double, reason).value
57
+ end
58
+
59
+ def object_list(reason = nil)
60
+ return if shift_nil?(reason)
61
+
62
+ shift(ObjectList, reason).length.times.map { object(reason && "object in object list for #{reason}") }
63
+ end
64
+
65
+ def object(reason = nil)
66
+ deserializer_for(shift(ClassNameRef, reason).value)[self]
67
+ end
68
+
69
+ def deserializer_for(class_ref_num)
70
+ @class_deserializer[class_ref_num].last
71
+ end
72
+
73
+ def shift_nil?(reason = nil)
74
+ shift(ObjectListNil, reason, raise: false)
75
+ end
76
+
77
+ def shift(cls, reason = nil, raise: true)
78
+ unless (token = @tokens.shift)
79
+ raise 'no more tokens'
80
+ end
81
+
82
+ unless token.is_a?(cls)
83
+ raise "expected #{cls} got #{token.inspect} for #{reason}" if raise
84
+
85
+ @tokens.unshift(token)
86
+ return
87
+ end
88
+ token
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SLF0
4
+ class Tokenizer
5
+ attr_reader :scanner
6
+ def initialize(slf0)
7
+ @scanner = StringScanner.new(slf0)
8
+ end
9
+ private_class_method :new
10
+
11
+ def self.tokenize(slf0)
12
+ new(slf0).tokenize
13
+ end
14
+
15
+ def tokenize
16
+ skip_header!
17
+ tokenize_body!
18
+ end
19
+
20
+ def skip_header!
21
+ raise 'missing header' unless scanner.skip(/SLF0/)
22
+ end
23
+
24
+ def tokenize_body!
25
+ body = []
26
+ until scanner.eos?
27
+ object = tokenize_field || tokenize_double_field || tokenize_object_list_nil
28
+ raise "malformed no object: #{scanner.rest.inspect}\n\nafter: #{body.inspect}" unless object
29
+
30
+ body << object
31
+ end
32
+ body
33
+ end
34
+
35
+ def tokenize_field
36
+ raise 'no int found' unless (int = scanner.scan(/\d+/).to_i)
37
+
38
+ case scanner.get_byte
39
+ when '#'
40
+ SLF0::Token::Int.new int
41
+ when '%'
42
+ SLF0::Token::ClassName.new scanner.scan(/.{#{int}}/).freeze
43
+ when '@'
44
+ SLF0::Token::ClassNameRef.new int
45
+ when '"'
46
+ SLF0::Token::String.new scanner.scan(/.{#{int}}/).tr("\r", "\n").freeze
47
+ when '('
48
+ SLF0::Token::ObjectList.new int
49
+ else
50
+ scanner.unscan
51
+ nil
52
+ end
53
+ end
54
+
55
+ def tokenize_double_field
56
+ return unless (hex = scanner.scan(/[0-9a-fA-F]*\^/)&.chomp('^'))
57
+
58
+ double = [hex.to_i(16)].pack('Q<').unpack1('G')
59
+ SLF0::Token::Double.new double
60
+ end
61
+
62
+ def tokenize_object_list_nil
63
+ return unless scanner.skip(/-/)
64
+
65
+ SLF0::Token::ObjectListNil.new nil
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+
5
+ module XCActivityLog
6
+ class Error < StandardError; end
7
+
8
+ autoload :Parser, 'xcactivitylog/parser'
9
+
10
+ def self.parse_file(path:)
11
+ contents = File.open(path, 'rb', &:read)
12
+ unzipped_contents = Zlib.gunzip(contents)
13
+
14
+ Parser.new(class_deserializer: {}).parse!(unzipped_contents)
15
+ end
16
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XCActivityLog
4
+ class SerializedObject
5
+ Attribute = Struct.new(:name, :type, :first_version, :last_version)
6
+ def self.attribute(name, type, first_version = 0, last_version = 99_999)
7
+ attr_reader name
8
+ alias_method "#{name}?", name if type == :boolean
9
+ if type == :time
10
+ define_method(:"#{name}_usec") do
11
+ time = send(name)
12
+ time.to_i * 1_000_000 + time.usec
13
+ end
14
+ end
15
+ attributes << Attribute.new(name, type, first_version, last_version).freeze
16
+ end
17
+
18
+ def self.attributes
19
+ @attributes ||= begin
20
+ SerializedObject == self ? [].freeze : superclass.attributes.dup
21
+ end
22
+ end
23
+
24
+ def hash
25
+ self.class.attributes.reduce(0x747) do |hash, attr|
26
+ hash ^ send(attr.name).hash
27
+ end
28
+ end
29
+
30
+ def ==(other)
31
+ self.class.attributes.reduce(self.class == other.class) do |eq, attr|
32
+ eq && (send(attr.name) == other.send(attr.name))
33
+ end
34
+ end
35
+
36
+ def eql?(other)
37
+ self.class.attributes.reduce(self.class == other.class) do |eq, attr|
38
+ eq && send(attr.name).eql?(other.send(attr.name))
39
+ end
40
+ end
41
+ end
42
+
43
+ class NSRange < SerializedObject
44
+ attribute :location, :int
45
+ attribute :length, :int
46
+ attributes.freeze
47
+ end
48
+
49
+ class IDEActivityLogSection < SerializedObject
50
+ include Enumerable
51
+ def each(&blk)
52
+ return enum_for(__method__) unless block_given?
53
+
54
+ yield self
55
+ subsections&.each { |s| s.each(&blk) }
56
+ end
57
+
58
+ def duration_usec
59
+ time_stopped_recording_usec - time_started_recording_usec
60
+ end
61
+ attribute :section_type, :int
62
+ attribute :domain_type, :string
63
+ attribute :title, :string
64
+ attribute :signature, :string
65
+ attribute :time_started_recording, :time
66
+ attribute :time_stopped_recording, :time
67
+ attribute :subsections, :object_list
68
+ attribute :text, :string
69
+ attribute :messages, :object_list
70
+ attribute :cancelled, :boolean
71
+ attribute :quiet, :boolean
72
+ attribute :fetched_from_cache, :boolean
73
+ attribute :subtitle, :string
74
+ attribute :location, :document_location
75
+ attribute :command_detail_description, :string
76
+ attribute :unique_identifier, :string
77
+ attribute :localized_result_string, :string
78
+ attribute :xcbuild_signature, :string
79
+ attribute :collect_metrics, :boolean, 9
80
+ attributes.freeze
81
+ end
82
+ class IDECommandLineBuildLog < IDEActivityLogSection
83
+ attributes.freeze
84
+ end
85
+
86
+ class IDEActivityLogMessage < SerializedObject
87
+ include Enumerable
88
+ def each(&blk)
89
+ return enum_for(__method__) unless block_given?
90
+
91
+ yield self
92
+ submessages&.each { |s| s.each(&blk) }
93
+ end
94
+ attribute :title, :string
95
+ attribute :short_title, :string
96
+ attribute :time_emitted, :int
97
+ attribute :range_in_section_text, :nsrange
98
+ attribute :submessages, :object_list
99
+ attribute :severity, :int
100
+ attribute :type, :string
101
+ attribute :location, :object
102
+ attribute :category_identifier, :string
103
+ attribute :secondary_locations, :object_list
104
+ attribute :additional_description, :string
105
+ attributes.freeze
106
+ end
107
+ class IDEClangDiagnosticActivityLogMessage < IDEActivityLogMessage
108
+ attributes.freeze
109
+ end
110
+
111
+ class DVTDocumentLocation < SerializedObject
112
+ attribute :document_url_string, :string
113
+ attribute :timestamp, :double
114
+ attributes.freeze
115
+
116
+ def document_url
117
+ URI::File.parse(document_url_string)
118
+ end
119
+ end
120
+ class DVTTextDocumentLocation < DVTDocumentLocation
121
+ attribute :starting_line_number, :int
122
+ attribute :starting_column_number, :int
123
+ attribute :ending_line_number, :int
124
+ attribute :ending_column_number, :int
125
+ attribute :character_range, :nsrange
126
+ attribute :location_encoding, :int
127
+ attributes.freeze
128
+ end
129
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slf0/parser'
4
+ require 'xcactivitylog/objects'
5
+
6
+ module XCActivityLog
7
+ class Parser < SLF0::Parser
8
+ class S < SLF0::Token::Stream
9
+ def initialize(tokens, class_deserializer:)
10
+ super(tokens, class_deserializer: class_deserializer)
11
+ @version = int('activity log version')
12
+ end
13
+
14
+ def deserializer_for(class_ref_num)
15
+ class_name, = @class_deserializer[class_ref_num]
16
+ cls = XCActivityLog.const_get(class_name)
17
+ raise "invalid #{class_name} #{cls}" unless cls < SerializedObject
18
+
19
+ lambda do |stream|
20
+ deserialize_instance_of(stream, cls)
21
+ end
22
+ end
23
+
24
+ def deserialize_instance_of(stream, cls)
25
+ instance = cls.new
26
+ cls.attributes.each do |attr|
27
+ next if attr.first_version > @version || attr.last_version < @version
28
+
29
+ value = stream.send(attr.type, "#{attr.name} for #{cls.name.split('::').last}")
30
+ instance.instance_variable_set(:"@#{attr.name}", value)
31
+ end
32
+ instance.freeze
33
+ end
34
+
35
+ def boolean(reason = nil)
36
+ int(reason) != 0
37
+ end
38
+
39
+ def nsrange(_reason = nil)
40
+ deserialize_instance_of(self, NSRange)
41
+ end
42
+
43
+ def document_location(reason = nil)
44
+ return if shift_nil?
45
+
46
+ object(reason).tap do |o|
47
+ raise "expected location, got #{o.class.name} for #{reason}" unless o.is_a?(DVTDocumentLocation)
48
+ end
49
+ end
50
+
51
+ EPOCH = Time.new(2001, 1, 1, 0, 0, 0, '+00:00').freeze
52
+
53
+ def time(reason = nil)
54
+ EPOCH.+(double(reason)).freeze
55
+ end
56
+ end
57
+ def make_stream(tokens, class_deserializer)
58
+ S.new(tokens, class_deserializer: class_deserializer)
59
+ end
60
+ end
61
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xcactivitylog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Giddins
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
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: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - segiddins@segiddins.me
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - CODE_OF_CONDUCT.md
63
+ - LICENSE.txt
64
+ - README.md
65
+ - VERSION
66
+ - lib/slf0.rb
67
+ - lib/slf0/parser.rb
68
+ - lib/slf0/token.rb
69
+ - lib/slf0/tokenizer.rb
70
+ - lib/xcactivitylog.rb
71
+ - lib/xcactivitylog/objects.rb
72
+ - lib/xcactivitylog/parser.rb
73
+ homepage: https://github.com/segiddins/xcactivitylog
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ homepage_uri: https://github.com/segiddins/xcactivitylog
78
+ source_code_uri: https://github.com/segiddins/xcactivitylog
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.7.6
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Parse Xcode's xcactivitylog files (and other SLF0-serialized files)
99
+ test_files: []