File: Distribution.rakumod

package info (click to toggle)
raku-zef 0.13.8-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 664 kB
  • sloc: perl: 22; makefile: 8
file content (234 lines) | stat: -rw-r--r-- 9,457 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use Zef;
use Zef::Distribution::DependencySpecification;
use Zef::Utils::SystemQuery;

class Zef::Distribution does Distribution is Zef::Distribution::DependencySpecification {

    =begin pod

    =title class Zef::Distribution

    =subtitle A generic Distribution implementation

    =head1 Synopsis

    =begin code :lang<raku>

        use Zef::Distribution;
        use JSON::Fast;

        my %meta = from-json("META6.json".IO.slurp);
        my $dist = Zef::Distribution.new(|%meta);

        # Show the meta data
        say $dist.meta.perl;

        # Output if the $dist contains a namespace matching Foo::Bar:ver<1>
        say $dist.contains-spec("Foo::Bar:ver<1>");

    =end code

    =head1 Description

    A C<Distribution> implementation that is used to represent not-yet-downloaded distributions.
    Generally you should use this class using the C<Distribution> interface and not as a struct representation
    of META6 data -- i.e. use C<$dist.meta.hash{"version"}> instead of <$dist.ver>. These variations are a wart
    included mostly for backwards compatibility purposes (or just leftover from years of changes to e.g. C<CompUnit::Repository>
    and friends).

    When using this class "best practice" would be to consider the following methods are the public api:
    C<meta> and C<content> (the C<Distribution> interface methods), C<depends-specs>, C<test-depends-specs>, C<build-depends-specs>, C<provides-specs>, C<provides-spec-matcher>, C<contains-spec>, C<Str>

    =head1 Methods

    =head2 method meta

        method meta(--> Hash:D)

    Returns the meta data that represents the distribution.

    =head2 method content

        method content()

    Will always throw an exception. This class is primarily used to represent a distribution when all we have it meta data; use
    a class like C<Zef::Distribution::Local> (which subclasses this class) if or when you need access to the content of files
    besides C<META6.json>.

    =head2 method depends-specs

        method depends-specs(--> Array[Zef::Distribution::DependencySpecification])

    Return an C<Array> of C<DependencySpecification> for the runtime dependencies of the distribution.

    =head2 method test-depends-specs

        method test-depends-specs(--> Array[Zef::Distribution::DependencySpecification])

    Return an C<Array> of C<DependencySpecification> for the test dependencies of the distribution.

    =head2 method depends-specs

        method build-depends-specs(--> Array[Zef::Distribution::DependencySpecification])

    Return an C<Array> of C<DependencySpecification> for the build dependencies of the distribution.

    =head2 method provides-specs

        method provides-specs(--> Array[Zef::Distribution::DependencySpecification])

    Return an C<Array> of C<DependencySpecification> for the namespaces in the distributions C<provides>.

    =head2 method provides-spec-matcher

        method provides-spec-matcher(Zef::Distribution::DependencySpecification $spec, :$strict --> Bool:D) { self.provides-specs.first({ ?$_.spec-matcher($spec, :$strict) }) }

    Returns C<True> if C<$spec> matches any namespaces this distribution provides (but not the name of the distribution itself).
    If C<$strict> is C<False> then partial name matches will be allowed (i.e. C<HTTP> matching C<HTTP::UserAgent>).

    =head2 method contains-spec

        multi method contains-spec(Str $spec, |c --> Bool:D)
        multi method contains-spec(Zef::Distribution::DependencySpecification $spec, Bool :$strict = True --> Bool:D)
        multi method contains-spec(Zef::Distribution::DependencySpecification::Any $spec, Bool :$strict = True --> Bool:D)

    Returns C<True> if C<$spec> matches any namespace this distribution provides, including the name of the distribution itself.
    If C<$strict> is C<False> then partial name matches will be allowed (i.e. C<HTTP> matching C<HTTP::UserAgent>).

    When given a C<Str> C<$spec> the C<$spec> will be turned into a C<Zef::Distribution::DependencySpecification>.

    =head2 method Str

        method Str(--> Str)

    Returns the explicit full name of the distribution, i.e. C<Foo> -> C<Foo:ver<>:auth<>:api<>>

    =head2 method id

        method id(--> Str)

    Returns a file system safe unique string identifier for the distribution. This is generally meant for internal use only.

    Note: This should not publicly be relied on for matching any C<Raku> implementation details this may appear to be emulating.

    =end pod


    has $.meta-version;
    has $.name;
    has $.auth;
    has $.author;
    has $.authority;
    has $.api;
    has $.ver;
    has $.version;
    has $.description;
    has $.depends;
    has %.provides;
    has %.files;
    has $.source-url;
    has $.license;
    has $.build-depends;
    has $.test-depends;
    has @.resources;
    has %.support;
    has $.builder;

    has %!meta;

    # attach arbitrary data, like for topological sort, that won't be saved on install
    has %.metainfo is rw;

    method new(*%_) { self.bless(|%_, :meta(%_)) }

    submethod TWEAK(:%!meta, :@!resources --> Nil) {
        @!resources = @!resources.flatmap(*.flat);
    }

    method auth { with $!auth { .Str } else { Nil } }
    method ver  { with $!ver // $!version { $!ver ~~ Version ?? $_ !! $!ver = Version.new($_ // 0) } }
    method api  { with $!api { $!api ~~ Version ?? $_ !! $!api = Version.new($_ // 0) } }

    # 'new-depends' refers to the hash form of `depends`
    has $!new-depends-cache;
    method !new-depends($type) {
        return Empty unless $.depends ~~ Hash;
        $!new-depends-cache := system-collapse($.depends) unless $!new-depends-cache.defined;
        return system-collapse($.depends){$type}.grep(*.defined).grep(*.<requires>).map(*.<requires>).map(*.Slip).Slip;
    }
    method !depends2specs(*@depends --> Array[DependencySpecification]) {
        my $depends := @depends.map({$_ ~~ List ?? $_.Slip !! $_ }).grep(*.defined);
        my DependencySpecification @depends-specs = $depends.map({ Zef::Distribution::DependencySpecification.new($_) }).grep(*.name);
        return @depends-specs;
    }

    method depends-specs(--> Array[DependencySpecification]) {
        my $depends := system-collapse($.depends);
        my $deps    := $.depends ~~ Hash ?? self!new-depends('runtime') !! $depends;
        return self!depends2specs($deps);
    }
    method build-depends-specs(--> Array[DependencySpecification]) {
        my $orig-build-depends := system-collapse($.build-depends);
        my $new-build-depends  := self!new-depends('build');
        return self!depends2specs(|$orig-build-depends, $new-build-depends);
    }
    method test-depends-specs(--> Array[DependencySpecification]) {
        my $orig-test-depends := system-collapse($.test-depends);
        my $new-test-depends  := self!new-depends('test');
        return self!depends2specs(|$orig-test-depends, $new-test-depends);
    }

    # make locating a module that is part of a distribution (ex. URI::Escape of URI) easier.
    # it doesn't need to be a hash mapping as its just for matching
    has @!provides-specs;
    method provides-specs(--> Array[DependencySpecification]) {
        return @!provides-specs if @!provides-specs.elems;
        my DependencySpecification @provides-specs = self.meta<provides>.grep(*.defined).map({
            # if $spec.name is not defined then .key (the module name of the current provides)
            # is not a valid module name (according to Zef::Identity grammar anyway). I ran into
            # this problem with `NativeCall::Errno` where one of the provides was: `X:NativeCall::Errorno`
            # The single colon cannot just be fixed to DWIM because that could just as easily denote
            # an identity part (identity parts are separated by a *single* colon; double colon is left alone)
            my $spec = Zef::Distribution::DependencySpecification.new(self!long-name(.key));
            next unless defined($spec.name);
            $spec;
        }).grep(*.defined).Slip;
        return @!provides-specs := @provides-specs;
    }

    method provides-spec-matcher(DependencySpecification  $spec, :$strict --> Bool:D) {
        return so self.provides-specs.first({ ?$_.spec-matcher($spec, :$strict) })
    }

    proto method contains-spec(|) {*}
    multi method contains-spec(Str $spec, |c --> Bool:D)
        { samewith( Zef::Distribution::DependencySpecification.new($spec, |c) ) }
    multi method contains-spec(Zef::Distribution::DependencySpecification $spec, Bool :$strict = True --> Bool:D)
        { return so self.spec-matcher($spec, :$strict) || self.provides-spec-matcher($spec, :$strict)  }
    multi method contains-spec(Zef::Distribution::DependencySpecification::Any $spec, Bool :$strict = True --> Bool:D)
        { return so self.contains-spec(any($spec.specs), :$strict) }

    method Str(--> Str) {
        return self!long-name($!name);
    }

    method !long-name($name --> Str) {
        return sprintf '%s:ver<%s>:auth<%s>:api<%s>',
            $name,
            (self.ver  // ''),
            (self.auth // '').trans(['<', '>'] => ['\<', '\>']),
            (self.api  // ''),
        ;
    }

    method id(--> Str) {
        use nqp;
        return nqp::sha1(self.Str);
    }

    method meta(--> Hash:D) { return %!meta }

    method content(|) {
        die "this method must be subclassed by something that can read from a content store";
    }
}