tangled
alpha
login
or
join now
tgirl.cloud
/
locker
1
fork
atom
a linter for your flake.lock file
1
fork
atom
overview
issues
pulls
pipelines
feat: init
isabelroses.com
7 months ago
12dd7627
+759
11 changed files
expand all
collapse all
unified
split
.envrc
.gitignore
Cargo.lock
Cargo.toml
LICENSE
README.md
default.nix
flake.lock
flake.nix
shell.nix
src
main.rs
+3
.envrc
···
1
1
+
if has nix; then
2
2
+
use flake
3
3
+
fi
+1
.gitignore
···
1
1
+
target
+135
Cargo.lock
···
1
1
+
# This file is automatically @generated by Cargo.
2
2
+
# It is not intended for manual editing.
3
3
+
version = 4
4
4
+
5
5
+
[[package]]
6
6
+
name = "argh"
7
7
+
version = "0.1.13"
8
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
9
+
checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240"
10
10
+
dependencies = [
11
11
+
"argh_derive",
12
12
+
"argh_shared",
13
13
+
"rust-fuzzy-search",
14
14
+
]
15
15
+
16
16
+
[[package]]
17
17
+
name = "argh_derive"
18
18
+
version = "0.1.13"
19
19
+
source = "registry+https://github.com/rust-lang/crates.io-index"
20
20
+
checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803"
21
21
+
dependencies = [
22
22
+
"argh_shared",
23
23
+
"proc-macro2",
24
24
+
"quote",
25
25
+
"syn",
26
26
+
]
27
27
+
28
28
+
[[package]]
29
29
+
name = "argh_shared"
30
30
+
version = "0.1.13"
31
31
+
source = "registry+https://github.com/rust-lang/crates.io-index"
32
32
+
checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6"
33
33
+
dependencies = [
34
34
+
"serde",
35
35
+
]
36
36
+
37
37
+
[[package]]
38
38
+
name = "itoa"
39
39
+
version = "1.0.15"
40
40
+
source = "registry+https://github.com/rust-lang/crates.io-index"
41
41
+
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
42
42
+
43
43
+
[[package]]
44
44
+
name = "locker"
45
45
+
version = "0.0.1"
46
46
+
dependencies = [
47
47
+
"argh",
48
48
+
"serde",
49
49
+
"serde_json",
50
50
+
]
51
51
+
52
52
+
[[package]]
53
53
+
name = "memchr"
54
54
+
version = "2.7.5"
55
55
+
source = "registry+https://github.com/rust-lang/crates.io-index"
56
56
+
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
57
57
+
58
58
+
[[package]]
59
59
+
name = "proc-macro2"
60
60
+
version = "1.0.95"
61
61
+
source = "registry+https://github.com/rust-lang/crates.io-index"
62
62
+
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
63
63
+
dependencies = [
64
64
+
"unicode-ident",
65
65
+
]
66
66
+
67
67
+
[[package]]
68
68
+
name = "quote"
69
69
+
version = "1.0.40"
70
70
+
source = "registry+https://github.com/rust-lang/crates.io-index"
71
71
+
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
72
72
+
dependencies = [
73
73
+
"proc-macro2",
74
74
+
]
75
75
+
76
76
+
[[package]]
77
77
+
name = "rust-fuzzy-search"
78
78
+
version = "0.1.1"
79
79
+
source = "registry+https://github.com/rust-lang/crates.io-index"
80
80
+
checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2"
81
81
+
82
82
+
[[package]]
83
83
+
name = "ryu"
84
84
+
version = "1.0.20"
85
85
+
source = "registry+https://github.com/rust-lang/crates.io-index"
86
86
+
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
87
87
+
88
88
+
[[package]]
89
89
+
name = "serde"
90
90
+
version = "1.0.219"
91
91
+
source = "registry+https://github.com/rust-lang/crates.io-index"
92
92
+
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
93
93
+
dependencies = [
94
94
+
"serde_derive",
95
95
+
]
96
96
+
97
97
+
[[package]]
98
98
+
name = "serde_derive"
99
99
+
version = "1.0.219"
100
100
+
source = "registry+https://github.com/rust-lang/crates.io-index"
101
101
+
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
102
102
+
dependencies = [
103
103
+
"proc-macro2",
104
104
+
"quote",
105
105
+
"syn",
106
106
+
]
107
107
+
108
108
+
[[package]]
109
109
+
name = "serde_json"
110
110
+
version = "1.0.141"
111
111
+
source = "registry+https://github.com/rust-lang/crates.io-index"
112
112
+
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
113
113
+
dependencies = [
114
114
+
"itoa",
115
115
+
"memchr",
116
116
+
"ryu",
117
117
+
"serde",
118
118
+
]
119
119
+
120
120
+
[[package]]
121
121
+
name = "syn"
122
122
+
version = "2.0.104"
123
123
+
source = "registry+https://github.com/rust-lang/crates.io-index"
124
124
+
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
125
125
+
dependencies = [
126
126
+
"proc-macro2",
127
127
+
"quote",
128
128
+
"unicode-ident",
129
129
+
]
130
130
+
131
131
+
[[package]]
132
132
+
name = "unicode-ident"
133
133
+
version = "1.0.18"
134
134
+
source = "registry+https://github.com/rust-lang/crates.io-index"
135
135
+
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+13
Cargo.toml
···
1
1
+
[package]
2
2
+
name = "locker"
3
3
+
version = "0.0.1"
4
4
+
license = "EUPL-1.2"
5
5
+
description = "linting for your flake.lock"
6
6
+
homepage = "https://github.com/isabelroses/locker"
7
7
+
authors = ["isabel roses"]
8
8
+
edition = "2024"
9
9
+
10
10
+
[dependencies]
11
11
+
argh = "0.1.13"
12
12
+
serde = { version = "1.0.219", features = ["derive"] }
13
13
+
serde_json = "1.0.141"
+287
LICENSE
···
1
1
+
EUROPEAN UNION PUBLIC LICENCE v. 1.2
2
2
+
EUPL © the European Union 2007, 2016
3
3
+
4
4
+
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
5
5
+
below) which is provided under the terms of this Licence. Any use of the Work,
6
6
+
other than as authorised under this Licence is prohibited (to the extent such
7
7
+
use is covered by a right of the copyright holder of the Work).
8
8
+
9
9
+
The Work is provided under the terms of this Licence when the Licensor (as
10
10
+
defined below) has placed the following notice immediately following the
11
11
+
copyright notice for the Work:
12
12
+
13
13
+
Licensed under the EUPL
14
14
+
15
15
+
or has expressed by any other means his willingness to license under the EUPL.
16
16
+
17
17
+
1. Definitions
18
18
+
19
19
+
In this Licence, the following terms have the following meaning:
20
20
+
21
21
+
- ‘The Licence’: this Licence.
22
22
+
23
23
+
- ‘The Original Work’: the work or software distributed or communicated by the
24
24
+
Licensor under this Licence, available as Source Code and also as Executable
25
25
+
Code as the case may be.
26
26
+
27
27
+
- ‘Derivative Works’: the works or software that could be created by the
28
28
+
Licensee, based upon the Original Work or modifications thereof. This Licence
29
29
+
does not define the extent of modification or dependence on the Original Work
30
30
+
required in order to classify a work as a Derivative Work; this extent is
31
31
+
determined by copyright law applicable in the country mentioned in Article 15.
32
32
+
33
33
+
- ‘The Work’: the Original Work or its Derivative Works.
34
34
+
35
35
+
- ‘The Source Code’: the human-readable form of the Work which is the most
36
36
+
convenient for people to study and modify.
37
37
+
38
38
+
- ‘The Executable Code’: any code which has generally been compiled and which is
39
39
+
meant to be interpreted by a computer as a program.
40
40
+
41
41
+
- ‘The Licensor’: the natural or legal person that distributes or communicates
42
42
+
the Work under the Licence.
43
43
+
44
44
+
- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
45
45
+
Licence, or otherwise contributes to the creation of a Derivative Work.
46
46
+
47
47
+
- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
48
48
+
the Work under the terms of the Licence.
49
49
+
50
50
+
- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
51
51
+
renting, distributing, communicating, transmitting, or otherwise making
52
52
+
available, online or offline, copies of the Work or providing access to its
53
53
+
essential functionalities at the disposal of any other natural or legal
54
54
+
person.
55
55
+
56
56
+
2. Scope of the rights granted by the Licence
57
57
+
58
58
+
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
59
59
+
sublicensable licence to do the following, for the duration of copyright vested
60
60
+
in the Original Work:
61
61
+
62
62
+
- use the Work in any circumstance and for all usage,
63
63
+
- reproduce the Work,
64
64
+
- modify the Work, and make Derivative Works based upon the Work,
65
65
+
- communicate to the public, including the right to make available or display
66
66
+
the Work or copies thereof to the public and perform publicly, as the case may
67
67
+
be, the Work,
68
68
+
- distribute the Work or copies thereof,
69
69
+
- lend and rent the Work or copies thereof,
70
70
+
- sublicense rights in the Work or copies thereof.
71
71
+
72
72
+
Those rights can be exercised on any media, supports and formats, whether now
73
73
+
known or later invented, as far as the applicable law permits so.
74
74
+
75
75
+
In the countries where moral rights apply, the Licensor waives his right to
76
76
+
exercise his moral right to the extent allowed by law in order to make effective
77
77
+
the licence of the economic rights here above listed.
78
78
+
79
79
+
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
80
80
+
any patents held by the Licensor, to the extent necessary to make use of the
81
81
+
rights granted on the Work under this Licence.
82
82
+
83
83
+
3. Communication of the Source Code
84
84
+
85
85
+
The Licensor may provide the Work either in its Source Code form, or as
86
86
+
Executable Code. If the Work is provided as Executable Code, the Licensor
87
87
+
provides in addition a machine-readable copy of the Source Code of the Work
88
88
+
along with each copy of the Work that the Licensor distributes or indicates, in
89
89
+
a notice following the copyright notice attached to the Work, a repository where
90
90
+
the Source Code is easily and freely accessible for as long as the Licensor
91
91
+
continues to distribute or communicate the Work.
92
92
+
93
93
+
4. Limitations on copyright
94
94
+
95
95
+
Nothing in this Licence is intended to deprive the Licensee of the benefits from
96
96
+
any exception or limitation to the exclusive rights of the rights owners in the
97
97
+
Work, of the exhaustion of those rights or of other applicable limitations
98
98
+
thereto.
99
99
+
100
100
+
5. Obligations of the Licensee
101
101
+
102
102
+
The grant of the rights mentioned above is subject to some restrictions and
103
103
+
obligations imposed on the Licensee. Those obligations are the following:
104
104
+
105
105
+
Attribution right: The Licensee shall keep intact all copyright, patent or
106
106
+
trademarks notices and all notices that refer to the Licence and to the
107
107
+
disclaimer of warranties. The Licensee must include a copy of such notices and a
108
108
+
copy of the Licence with every copy of the Work he/she distributes or
109
109
+
communicates. The Licensee must cause any Derivative Work to carry prominent
110
110
+
notices stating that the Work has been modified and the date of modification.
111
111
+
112
112
+
Copyleft clause: If the Licensee distributes or communicates copies of the
113
113
+
Original Works or Derivative Works, this Distribution or Communication will be
114
114
+
done under the terms of this Licence or of a later version of this Licence
115
115
+
unless the Original Work is expressly distributed only under this version of the
116
116
+
Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
117
117
+
(becoming Licensor) cannot offer or impose any additional terms or conditions on
118
118
+
the Work or Derivative Work that alter or restrict the terms of the Licence.
119
119
+
120
120
+
Compatibility clause: If the Licensee Distributes or Communicates Derivative
121
121
+
Works or copies thereof based upon both the Work and another work licensed under
122
122
+
a Compatible Licence, this Distribution or Communication can be done under the
123
123
+
terms of this Compatible Licence. For the sake of this clause, ‘Compatible
124
124
+
Licence’ refers to the licences listed in the appendix attached to this Licence.
125
125
+
Should the Licensee's obligations under the Compatible Licence conflict with
126
126
+
his/her obligations under this Licence, the obligations of the Compatible
127
127
+
Licence shall prevail.
128
128
+
129
129
+
Provision of Source Code: When distributing or communicating copies of the Work,
130
130
+
the Licensee will provide a machine-readable copy of the Source Code or indicate
131
131
+
a repository where this Source will be easily and freely available for as long
132
132
+
as the Licensee continues to distribute or communicate the Work.
133
133
+
134
134
+
Legal Protection: This Licence does not grant permission to use the trade names,
135
135
+
trademarks, service marks, or names of the Licensor, except as required for
136
136
+
reasonable and customary use in describing the origin of the Work and
137
137
+
reproducing the content of the copyright notice.
138
138
+
139
139
+
6. Chain of Authorship
140
140
+
141
141
+
The original Licensor warrants that the copyright in the Original Work granted
142
142
+
hereunder is owned by him/her or licensed to him/her and that he/she has the
143
143
+
power and authority to grant the Licence.
144
144
+
145
145
+
Each Contributor warrants that the copyright in the modifications he/she brings
146
146
+
to the Work are owned by him/her or licensed to him/her and that he/she has the
147
147
+
power and authority to grant the Licence.
148
148
+
149
149
+
Each time You accept the Licence, the original Licensor and subsequent
150
150
+
Contributors grant You a licence to their contributions to the Work, under the
151
151
+
terms of this Licence.
152
152
+
153
153
+
7. Disclaimer of Warranty
154
154
+
155
155
+
The Work is a work in progress, which is continuously improved by numerous
156
156
+
Contributors. It is not a finished work and may therefore contain defects or
157
157
+
‘bugs’ inherent to this type of development.
158
158
+
159
159
+
For the above reason, the Work is provided under the Licence on an ‘as is’ basis
160
160
+
and without warranties of any kind concerning the Work, including without
161
161
+
limitation merchantability, fitness for a particular purpose, absence of defects
162
162
+
or errors, accuracy, non-infringement of intellectual property rights other than
163
163
+
copyright as stated in Article 6 of this Licence.
164
164
+
165
165
+
This disclaimer of warranty is an essential part of the Licence and a condition
166
166
+
for the grant of any rights to the Work.
167
167
+
168
168
+
8. Disclaimer of Liability
169
169
+
170
170
+
Except in the cases of wilful misconduct or damages directly caused to natural
171
171
+
persons, the Licensor will in no event be liable for any direct or indirect,
172
172
+
material or moral, damages of any kind, arising out of the Licence or of the use
173
173
+
of the Work, including without limitation, damages for loss of goodwill, work
174
174
+
stoppage, computer failure or malfunction, loss of data or any commercial
175
175
+
damage, even if the Licensor has been advised of the possibility of such damage.
176
176
+
However, the Licensor will be liable under statutory product liability laws as
177
177
+
far such laws apply to the Work.
178
178
+
179
179
+
9. Additional agreements
180
180
+
181
181
+
While distributing the Work, You may choose to conclude an additional agreement,
182
182
+
defining obligations or services consistent with this Licence. However, if
183
183
+
accepting obligations, You may act only on your own behalf and on your sole
184
184
+
responsibility, not on behalf of the original Licensor or any other Contributor,
185
185
+
and only if You agree to indemnify, defend, and hold each Contributor harmless
186
186
+
for any liability incurred by, or claims asserted against such Contributor by
187
187
+
the fact You have accepted any warranty or additional liability.
188
188
+
189
189
+
10. Acceptance of the Licence
190
190
+
191
191
+
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
192
192
+
placed under the bottom of a window displaying the text of this Licence or by
193
193
+
affirming consent in any other similar way, in accordance with the rules of
194
194
+
applicable law. Clicking on that icon indicates your clear and irrevocable
195
195
+
acceptance of this Licence and all of its terms and conditions.
196
196
+
197
197
+
Similarly, you irrevocably accept this Licence and all of its terms and
198
198
+
conditions by exercising any rights granted to You by Article 2 of this Licence,
199
199
+
such as the use of the Work, the creation by You of a Derivative Work or the
200
200
+
Distribution or Communication by You of the Work or copies thereof.
201
201
+
202
202
+
11. Information to the public
203
203
+
204
204
+
In case of any Distribution or Communication of the Work by means of electronic
205
205
+
communication by You (for example, by offering to download the Work from a
206
206
+
remote location) the distribution channel or media (for example, a website) must
207
207
+
at least provide to the public the information requested by the applicable law
208
208
+
regarding the Licensor, the Licence and the way it may be accessible, concluded,
209
209
+
stored and reproduced by the Licensee.
210
210
+
211
211
+
12. Termination of the Licence
212
212
+
213
213
+
The Licence and the rights granted hereunder will terminate automatically upon
214
214
+
any breach by the Licensee of the terms of the Licence.
215
215
+
216
216
+
Such a termination will not terminate the licences of any person who has
217
217
+
received the Work from the Licensee under the Licence, provided such persons
218
218
+
remain in full compliance with the Licence.
219
219
+
220
220
+
13. Miscellaneous
221
221
+
222
222
+
Without prejudice of Article 9 above, the Licence represents the complete
223
223
+
agreement between the Parties as to the Work.
224
224
+
225
225
+
If any provision of the Licence is invalid or unenforceable under applicable
226
226
+
law, this will not affect the validity or enforceability of the Licence as a
227
227
+
whole. Such provision will be construed or reformed so as necessary to make it
228
228
+
valid and enforceable.
229
229
+
230
230
+
The European Commission may publish other linguistic versions or new versions of
231
231
+
this Licence or updated versions of the Appendix, so far this is required and
232
232
+
reasonable, without reducing the scope of the rights granted by the Licence. New
233
233
+
versions of the Licence will be published with a unique version number.
234
234
+
235
235
+
All linguistic versions of this Licence, approved by the European Commission,
236
236
+
have identical value. Parties can take advantage of the linguistic version of
237
237
+
their choice.
238
238
+
239
239
+
14. Jurisdiction
240
240
+
241
241
+
Without prejudice to specific agreement between parties,
242
242
+
243
243
+
- any litigation resulting from the interpretation of this License, arising
244
244
+
between the European Union institutions, bodies, offices or agencies, as a
245
245
+
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
246
246
+
of Justice of the European Union, as laid down in article 272 of the Treaty on
247
247
+
the Functioning of the European Union,
248
248
+
249
249
+
- any litigation arising between other parties and resulting from the
250
250
+
interpretation of this License, will be subject to the exclusive jurisdiction
251
251
+
of the competent court where the Licensor resides or conducts its primary
252
252
+
business.
253
253
+
254
254
+
15. Applicable Law
255
255
+
256
256
+
Without prejudice to specific agreement between parties,
257
257
+
258
258
+
- this Licence shall be governed by the law of the European Union Member State
259
259
+
where the Licensor has his seat, resides or has his registered office,
260
260
+
261
261
+
- this licence shall be governed by Belgian law if the Licensor has no seat,
262
262
+
residence or registered office inside a European Union Member State.
263
263
+
264
264
+
Appendix
265
265
+
266
266
+
‘Compatible Licences’ according to Article 5 EUPL are:
267
267
+
268
268
+
- GNU General Public License (GPL) v. 2, v. 3
269
269
+
- GNU Affero General Public License (AGPL) v. 3
270
270
+
- Open Software License (OSL) v. 2.1, v. 3.0
271
271
+
- Eclipse Public License (EPL) v. 1.0
272
272
+
- CeCILL v. 2.0, v. 2.1
273
273
+
- Mozilla Public Licence (MPL) v. 2
274
274
+
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
275
275
+
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
276
276
+
works other than software
277
277
+
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
278
278
+
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
279
279
+
Reciprocity (LiLiQ-R+).
280
280
+
281
281
+
The European Commission may update this Appendix to later versions of the above
282
282
+
licences without producing a new version of the EUPL, as long as they provide
283
283
+
the rights granted in Article 2 of this Licence and protect the covered Source
284
284
+
Code from exclusive appropriation.
285
285
+
286
286
+
All other changes or additions to this Appendix require the production of a new
287
287
+
EUPL version.
+5
README.md
···
1
1
+
## Locker Lint
2
2
+
3
3
+
Locker lint is a tool designed to lint your flake.lock file to find duplicate entries by their flake uri.
4
4
+
5
5
+
+28
default.nix
···
1
1
+
{ lib, rustPlatform }:
2
2
+
let
3
3
+
toml = (lib.importTOML ./Cargo.toml).package;
4
4
+
in
5
5
+
rustPlatform.buildRustPackage {
6
6
+
pname = "locker-rust";
7
7
+
inherit (toml) version;
8
8
+
9
9
+
src = lib.fileset.toSource {
10
10
+
root = ./.;
11
11
+
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) (
12
12
+
lib.fileset.unions [
13
13
+
./Cargo.toml
14
14
+
./Cargo.lock
15
15
+
./src
16
16
+
]
17
17
+
);
18
18
+
};
19
19
+
20
20
+
cargoLock.lockFile = ./Cargo.lock;
21
21
+
22
22
+
meta = {
23
23
+
inherit (toml) homepage description;
24
24
+
license = lib.licenses.eupl12;
25
25
+
maintainers = with lib.maintainers; [ isabelroses ];
26
26
+
mainProgram = "locker";
27
27
+
};
28
28
+
}
+27
flake.lock
···
1
1
+
{
2
2
+
"nodes": {
3
3
+
"nixpkgs": {
4
4
+
"locked": {
5
5
+
"lastModified": 1753432016,
6
6
+
"narHash": "sha256-cnL5WWn/xkZoyH/03NNUS7QgW5vI7D1i74g48qplCvg=",
7
7
+
"owner": "nixos",
8
8
+
"repo": "nixpkgs",
9
9
+
"rev": "6027c30c8e9810896b92429f0092f624f7b1aace",
10
10
+
"type": "github"
11
11
+
},
12
12
+
"original": {
13
13
+
"owner": "nixos",
14
14
+
"ref": "nixpkgs-unstable",
15
15
+
"repo": "nixpkgs",
16
16
+
"type": "github"
17
17
+
}
18
18
+
},
19
19
+
"root": {
20
20
+
"inputs": {
21
21
+
"nixpkgs": "nixpkgs"
22
22
+
}
23
23
+
}
24
24
+
},
25
25
+
"root": "root",
26
26
+
"version": 7
27
27
+
}
+27
flake.nix
···
1
1
+
{
2
2
+
inputs = {
3
3
+
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4
4
+
};
5
5
+
6
6
+
outputs =
7
7
+
{ self, nixpkgs }:
8
8
+
let
9
9
+
forAllSystems =
10
10
+
function:
11
11
+
nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (
12
12
+
system: function nixpkgs.legacyPackages.${system}
13
13
+
);
14
14
+
in
15
15
+
{
16
16
+
packages = forAllSystems (pkgs: {
17
17
+
locker = pkgs.callPackage ./default.nix { };
18
18
+
default = self.packages.${pkgs.stdenv.hostPlatform.system}.locker;
19
19
+
});
20
20
+
21
21
+
devShells = forAllSystems (pkgs: {
22
22
+
default = pkgs.callPackage ./shell.nix { };
23
23
+
});
24
24
+
25
25
+
overlays.default = final: _: { locker = final.callPackage ./default.nix { }; };
26
26
+
};
27
27
+
}
+26
shell.nix
···
1
1
+
{
2
2
+
mkShell,
3
3
+
callPackage,
4
4
+
rustPlatform,
5
5
+
6
6
+
# extra tooling
7
7
+
clippy,
8
8
+
rustfmt,
9
9
+
rust-analyzer,
10
10
+
}:
11
11
+
let
12
12
+
defaultPackage = callPackage ./default.nix { };
13
13
+
in
14
14
+
mkShell {
15
15
+
inputsFrom = [ defaultPackage ];
16
16
+
17
17
+
env = {
18
18
+
RUST_SRC_PATH = rustPlatform.rustLibSrc;
19
19
+
};
20
20
+
21
21
+
packages = [
22
22
+
clippy
23
23
+
rustfmt
24
24
+
rust-analyzer
25
25
+
];
26
26
+
}
+207
src/main.rs
···
1
1
+
use argh::FromArgs;
2
2
+
use serde::Deserialize;
3
3
+
use std::error::Error;
4
4
+
use std::fs;
5
5
+
use std::{collections::HashMap, path::PathBuf};
6
6
+
7
7
+
/// locker - a tool to lint your flake.lock file
8
8
+
#[derive(FromArgs)]
9
9
+
#[argh(help_triggers("-h", "--help"))]
10
10
+
struct Args {
11
11
+
#[argh(positional, default = "PathBuf::from(\"flake.lock\")")]
12
12
+
flake_lock: PathBuf,
13
13
+
}
14
14
+
15
15
+
#[derive(Deserialize, Debug)]
16
16
+
struct FlakeLock {
17
17
+
nodes: HashMap<String, Node>,
18
18
+
version: usize,
19
19
+
20
20
+
#[allow(dead_code)]
21
21
+
root: String,
22
22
+
}
23
23
+
24
24
+
#[derive(Deserialize, Debug)]
25
25
+
struct Node {
26
26
+
locked: Option<Locked>,
27
27
+
}
28
28
+
29
29
+
#[derive(Deserialize, Debug)]
30
30
+
struct Locked {
31
31
+
#[serde(rename = "type")]
32
32
+
node_type: String,
33
33
+
34
34
+
// for github, gitlab and sourcehut we have these fields
35
35
+
owner: Option<String>,
36
36
+
repo: Option<String>,
37
37
+
38
38
+
// for git, hg and tarball we have these fields
39
39
+
url: Option<String>,
40
40
+
41
41
+
// path
42
42
+
path: Option<String>,
43
43
+
}
44
44
+
45
45
+
fn main() -> Result<(), Box<dyn Error>> {
46
46
+
let args: Args = argh::from_env();
47
47
+
let flake_lock_content = fs::read_to_string(&args.flake_lock)?;
48
48
+
let flake_lock: FlakeLock = serde_json::from_str(&flake_lock_content)?;
49
49
+
50
50
+
if flake_lock.version != 7 {
51
51
+
eprintln!("Unsupported flake.lock version: {}", flake_lock.version);
52
52
+
std::process::exit(1);
53
53
+
}
54
54
+
55
55
+
let inputs = parse_inputs(flake_lock);
56
56
+
let duplicates = find_duplicates(inputs);
57
57
+
58
58
+
if duplicates.is_empty() {
59
59
+
println!("No duplicate inputs found.");
60
60
+
std::process::exit(0);
61
61
+
}
62
62
+
63
63
+
println!("The following flake uris contained duplicate entries in your flake.lock:");
64
64
+
for (input, dups) in duplicates {
65
65
+
eprintln!(" '{}': {}", input, dups.join(", "));
66
66
+
}
67
67
+
68
68
+
std::process::exit(1);
69
69
+
}
70
70
+
71
71
+
fn parse_inputs(flake_lock: FlakeLock) -> HashMap<String, String> {
72
72
+
let mut data = HashMap::new();
73
73
+
74
74
+
for (k, v) in flake_lock.nodes {
75
75
+
if v.locked.is_none() {
76
76
+
continue;
77
77
+
}
78
78
+
79
79
+
let val = flake_uri(v.locked.unwrap()).ok().unwrap_or_else(|| {
80
80
+
eprintln!("Failed to parse URI for input '{k}'");
81
81
+
String::new()
82
82
+
});
83
83
+
84
84
+
data.entry(k).insert_entry(val);
85
85
+
}
86
86
+
87
87
+
data
88
88
+
}
89
89
+
90
90
+
fn find_duplicates(inputs: HashMap<String, String>) -> HashMap<String, Vec<String>> {
91
91
+
let mut seen: Vec<String> = Vec::new();
92
92
+
let mut duplicates: HashMap<String, Vec<String>> = HashMap::new();
93
93
+
94
94
+
for (input_name, input_uri) in inputs {
95
95
+
if seen.contains(&input_uri) {
96
96
+
duplicates.entry(input_uri).or_default().push(input_name);
97
97
+
} else {
98
98
+
seen.push(input_uri);
99
99
+
}
100
100
+
}
101
101
+
102
102
+
duplicates
103
103
+
}
104
104
+
105
105
+
fn flake_uri(lock: Locked) -> Result<String, Box<dyn Error>> {
106
106
+
match lock.node_type.as_str() {
107
107
+
"github" | "gitlab" | "sourcehut" => Ok(format!(
108
108
+
"{}:{}/{}",
109
109
+
lock.node_type,
110
110
+
lock.owner.unwrap().to_lowercase(),
111
111
+
lock.repo.unwrap().to_lowercase()
112
112
+
)),
113
113
+
"git" | "hg" | "tarball" => Ok(format!(
114
114
+
"{}:{}",
115
115
+
lock.node_type,
116
116
+
lock.url.unwrap_or_default()
117
117
+
)),
118
118
+
"path" => Ok(format!(
119
119
+
"{}:{}",
120
120
+
lock.node_type,
121
121
+
lock.path.unwrap_or_default()
122
122
+
)),
123
123
+
_ => Err(format!("Unknown node type: {}", lock.node_type))?,
124
124
+
}
125
125
+
}
126
126
+
127
127
+
#[cfg(test)]
128
128
+
mod tests {
129
129
+
use super::*;
130
130
+
131
131
+
const FLAKE_LOCK: &str = r#"
132
132
+
{
133
133
+
"nodes": {
134
134
+
"input1": {
135
135
+
"locked": {
136
136
+
"type": "github",
137
137
+
"owner": "user1",
138
138
+
"repo": "repo1"
139
139
+
}
140
140
+
},
141
141
+
"input2": {
142
142
+
"locked": {
143
143
+
"type": "github",
144
144
+
"owner": "user2",
145
145
+
"repo": "repo2"
146
146
+
}
147
147
+
},
148
148
+
"input3": {
149
149
+
"locked": {
150
150
+
"type": "github",
151
151
+
"owner": "user1",
152
152
+
"repo": "repo1"
153
153
+
}
154
154
+
},
155
155
+
"input4": {
156
156
+
"locked": {
157
157
+
"type": "git",
158
158
+
"url": "https://example.com/repo.git"
159
159
+
}
160
160
+
},
161
161
+
"input5": {
162
162
+
"locked": {
163
163
+
"type": "git",
164
164
+
"url": "https://example.com/repo.git"
165
165
+
}
166
166
+
}
167
167
+
},
168
168
+
"version": 7,
169
169
+
"root": "."
170
170
+
}
171
171
+
"#;
172
172
+
173
173
+
#[test]
174
174
+
fn test_parse_inputs() {
175
175
+
let flake_lock: FlakeLock = serde_json::from_str(FLAKE_LOCK).unwrap();
176
176
+
let inputs = parse_inputs(flake_lock);
177
177
+
178
178
+
assert_eq!(inputs.len(), 5);
179
179
+
assert!(inputs.contains_key("input1"));
180
180
+
assert!(inputs.contains_key("input2"));
181
181
+
assert!(inputs.contains_key("input3"));
182
182
+
assert!(inputs.contains_key("input4"));
183
183
+
assert!(inputs.contains_key("input5"));
184
184
+
185
185
+
assert_eq!(inputs.get("input1").unwrap(), "github:user1/repo1");
186
186
+
assert_eq!(inputs.get("input2").unwrap(), "github:user2/repo2");
187
187
+
assert_eq!(inputs.get("input3").unwrap(), "github:user1/repo1");
188
188
+
assert_eq!(
189
189
+
inputs.get("input4").unwrap(),
190
190
+
"git:https://example.com/repo.git"
191
191
+
);
192
192
+
assert_eq!(
193
193
+
inputs.get("input5").unwrap(),
194
194
+
"git:https://example.com/repo.git"
195
195
+
);
196
196
+
}
197
197
+
198
198
+
#[test]
199
199
+
fn test_duplicates() {
200
200
+
let flake_lock: FlakeLock = serde_json::from_str(FLAKE_LOCK).unwrap();
201
201
+
202
202
+
let inputs = parse_inputs(flake_lock);
203
203
+
let duplicates = find_duplicates(inputs.clone());
204
204
+
205
205
+
assert_eq!(duplicates.len(), 2);
206
206
+
}
207
207
+
}