-
-
Notifications
You must be signed in to change notification settings - Fork 188
feat: scope deps #2133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: scope deps #2133
Changes from all commits
3087feb
bfd38a1
aa86bda
bdd4936
28d2eb3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package dev.jbang.dependencies; | ||
|
|
||
| import java.util.*; | ||
|
|
||
| import dev.jbang.util.AttributeParser; | ||
|
|
||
| public class DependencyAttributes { | ||
|
|
||
| /** default attributes */ | ||
| public static final DependencyAttributes DEFAULT = new DependencyAttributes( | ||
| AttributeParser.parseAttributeList("scope=build,run", "scope")); | ||
|
|
||
| private final Map<String, List<String>> attributes; | ||
|
|
||
| // Java 8 Set.of() is sad :) | ||
| static Set<String> defaultScopes = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("build", "run"))); | ||
| Set<String> scopes; | ||
|
|
||
| public DependencyAttributes(Map<String, List<String>> attributes) { | ||
| this.attributes = attributes; | ||
| } | ||
|
|
||
| // lazy computed; dont store hashset unless truly needed | ||
| public boolean includeInScope(String scope) { | ||
| if (scopes == null) { | ||
| if (attributes.containsKey("scope")) { | ||
| scopes = new HashSet<>(attributes.get("scope")); | ||
| } else { | ||
| scopes = defaultScopes; | ||
| } | ||
| } | ||
| return scopes.contains(scope); | ||
| } | ||
|
|
||
| /** | ||
| * is there something configured? even if default ? | ||
| * | ||
| * @return | ||
| */ | ||
| public boolean isDefault() { | ||
| return !attributes.isEmpty(); | ||
| } | ||
|
|
||
| public String toStringFormat() { | ||
| return AttributeParser.toStringRep(attributes, "scope"); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return toStringFormat(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so first push resulted in funky TestModules errors that was caused by equals method missing on Attributes - which is fair..but reveals a conondrum - how do we define equals for these? i've implemented a few tests (which currently fails) showing situations that we probably want to make equals...which we can by unifing attributes...but should we?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just did an update where i split it out in a DependencyRequest even though I do thing the other properties makes sense to have on the "thing" that encodes the dependency - but it seems like we have places where we treat MavenCoordinate as absolutes which can be used as keys....that is NOT the case even with these new info because MavenCoordinates can have version ranges defined. |
||
| // TODO: this wont make g:a:v and g:a:v{build,run} equal ...even if they are... | ||
| if (o == null || getClass() != o.getClass()) | ||
| return false; | ||
| DependencyAttributes that = (DependencyAttributes) o; | ||
| return Objects.equals(attributes, that.attributes); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hashCode(attributes); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| package dev.jbang.util; | ||
|
|
||
| import java.util.*; | ||
|
|
||
| //handle {} similar to how Asciidoc(tor) interpret attribute lists: https://docs.asciidoctor.org/asciidoc/latest/attributes/positional-and-named-attributes/ | ||
| //Derived from https://github.com/yupiik/tools-maven-plugin/blob/d1e8b97ebcae032684a0fafac426bcc25819089f/asciidoc-java/src/main/java/io/yupiik/asciidoc/parser/Parser.java#L1634 | ||
|
|
||
| public class AttributeParser { | ||
|
|
||
| public static Map<String, List<String>> parseAttributeList(String input, String defaultKey) { | ||
| Map<String, List<String>> result = new LinkedHashMap<>(); | ||
| List<String> unnamed = new ArrayList<>(); | ||
|
|
||
| for (String token : tokenize(input)) { | ||
| if (token.isEmpty()) | ||
| continue; | ||
|
|
||
| if (token.contains("=")) { | ||
| String[] kv = token.split("=", 2); | ||
| String key = kv[0].trim(); | ||
| String value = unquote(kv[1].trim()); | ||
|
|
||
| if (key.equals(defaultKey)) { | ||
| unnamed.add(value); | ||
| } else { | ||
| result.put(key, Collections.singletonList(value)); | ||
| } | ||
| } else if (token.contains("%")) { | ||
| String[] parts = token.split("%"); | ||
| int startIdx = 0; | ||
|
|
||
| if (!parts[0].isEmpty()) { | ||
| unnamed.add(unquote(parts[0])); | ||
| startIdx = 1; | ||
| } | ||
|
|
||
| for (int i = startIdx; i < parts.length; i++) { | ||
| if (!parts[i].isEmpty()) { | ||
| result.put(parts[i], Collections.singletonList("true")); | ||
| } | ||
| } | ||
| } else { | ||
| unnamed.add(unquote(token)); | ||
| } | ||
| } | ||
|
|
||
| if (!unnamed.isEmpty()) { | ||
| result.put(defaultKey, unnamed); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static List<String> tokenize(String input) { | ||
| if (input == null) | ||
| return Collections.emptyList(); | ||
| List<String> tokens = new ArrayList<>(); | ||
| StringBuilder current = new StringBuilder(); | ||
| boolean inSingleQuote = false, inDoubleQuote = false; | ||
|
|
||
| for (int i = 0; i < input.length(); i++) { | ||
| char c = input.charAt(i); | ||
|
|
||
| if (c == ',' && !inSingleQuote && !inDoubleQuote) { | ||
| tokens.add(current.toString().trim()); | ||
| current.setLength(0); | ||
| } else { | ||
| if (c == '"' && !inSingleQuote) { | ||
| if (i == 0 || input.charAt(i - 1) != '\\') { | ||
| inDoubleQuote = !inDoubleQuote; | ||
| } | ||
| } else if (c == '\'' && !inDoubleQuote) { | ||
| if (i == 0 || input.charAt(i - 1) != '\\') { | ||
| inSingleQuote = !inSingleQuote; | ||
| } | ||
| } | ||
| current.append(c); | ||
| } | ||
| } | ||
|
|
||
| if (current.length() > 0) { | ||
| tokens.add(current.toString().trim()); | ||
| } | ||
|
|
||
| return tokens; | ||
| } | ||
|
|
||
| private static String unquote(String s) { | ||
| if (s.length() < 2) | ||
| return s; | ||
|
|
||
| char first = s.charAt(0); | ||
| char last = s.charAt(s.length() - 1); | ||
|
|
||
| if ((first == '"' || first == '\'') && last != first) { | ||
| return s; | ||
| } | ||
|
|
||
| if ((first == '"' && last == '"') || (first == '\'' && last == '\'')) { | ||
| String inner = s.substring(1, s.length() - 1); | ||
| return first == '"' ? inner.replace("\\\"", "\"") : inner.replace("\\'", "'"); | ||
| } | ||
|
|
||
| return s; | ||
| } | ||
|
|
||
| private static String quoteValue(String value) { | ||
| boolean needsQuote = value.contains(",") || value.contains(" ") || value.contains("\"") || value.contains("'"); | ||
| if (!needsQuote) | ||
| return value; | ||
|
|
||
| boolean useDouble = !value.contains("\"") || value.contains("'"); | ||
| String escaped = value.replace(useDouble ? "\"" : "'", useDouble ? "\\\"" : "\\'"); | ||
| return useDouble ? "\"" + escaped + "\"" : "'" + escaped + "'"; | ||
| } | ||
|
|
||
| public static String toStringRep(Map<String, List<String>> attributes, String defaultKey) { | ||
| List<String> parts = new ArrayList<>(); | ||
|
|
||
| // Positional values first | ||
| List<String> positional = attributes.get(defaultKey); | ||
| if (positional != null) { | ||
| for (String value : positional) { | ||
| parts.add(quoteValue(value)); | ||
| } | ||
| } | ||
|
|
||
| // Other keys | ||
| for (Map.Entry<String, List<String>> entry : attributes.entrySet()) { | ||
| String key = entry.getKey(); | ||
| if (key.equals(defaultKey)) | ||
| continue; | ||
|
|
||
| List<String> values = entry.getValue(); | ||
| for (String value : values) { | ||
| if ("true".equals(value)) { | ||
| parts.add("%" + key); | ||
| } else { | ||
| parts.add(key + "=" + quoteValue(value)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return String.join(",", parts); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the word scope - mainly because these aren't actually maven scopes but rather something that gets translated into a scope.
includeInConfig(String config)maybe ? (gradle use configuration as name for different scenarios)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and it would be
defaultConfigsif follow same route.