From b4dc9e3cefc9ddf666790a92832ab3a73b39e29f Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Tue, 24 Sep 2024 23:51:04 +0200 Subject: [PATCH] add opt. snapshot to TemplateInfo experimental as described here: https://github.com/bndtools/bnd/issues/6286#issuecomment-2371800610 example: bndtools/workspace-templates/gradle#567648ff425693b27b191bd38ace7c9c10539c2d;name=b;description=B;snapshot=bndtools/workspace-templates/gradle#master This defines a default TemplateID to a specific commit SHA and a snapshot to the master (HEAD branch) Signed-off-by: Christoph Rueger --- .../wstemplates/FragmentTemplateEngine.java | 83 +++++++++++++++++-- .../src/aQute/bnd/wstemplates/TemplateID.java | 20 ++--- .../wstemplates/TemplateFragmentsTest.java | 31 ++++--- .../bndtools/wizards/newworkspace/Model.java | 8 +- .../newworkspace/NewWorkspaceWizard.java | 72 ++++++++++++---- .../SelectedTemplateInfoEditingSupport.java | 50 +++++++++++ 6 files changed, 216 insertions(+), 48 deletions(-) create mode 100644 bndtools.core/src/bndtools/wizards/newworkspace/SelectedTemplateInfoEditingSupport.java diff --git a/biz.aQute.bndlib/src/aQute/bnd/wstemplates/FragmentTemplateEngine.java b/biz.aQute.bndlib/src/aQute/bnd/wstemplates/FragmentTemplateEngine.java index 958cc22643..87411245fb 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/wstemplates/FragmentTemplateEngine.java +++ b/biz.aQute.bndlib/src/aQute/bnd/wstemplates/FragmentTemplateEngine.java @@ -57,6 +57,7 @@ public class FragmentTemplateEngine { private static final String DESCRIPTION = "description"; private static final String WORKSPACE_TEMPLATES = "-workspace-templates"; private static final String NAME = "name"; + private static final String SNAPSHOT = "snapshot"; final static Logger log = LoggerFactory.getLogger(FragmentTemplateEngine.class); final List templates = new ArrayList<>(); @@ -75,7 +76,8 @@ public enum UpdateStatus { * Info about a template, comes from the index files. */ - public record TemplateInfo(TemplateID id, String name, String description, String[] require, String... tag) + public record TemplateInfo(TemplateID id, String name, String description, TemplateID snapshotId, String[] require, + String... tag) implements Comparable { @Override @@ -84,6 +86,66 @@ public int compareTo(TemplateInfo o) { } } + /** + * Represents a TemplateInfo with an additional flag indicating if the + * default version or snapshot version should be downloaded. We cannot use + * java record because we need useSnapshot to be mutable because we render + * it as a checkbox in the table + */ + public static class SelectedTemplateInfo implements Comparable { + + final TemplateInfo templateInfo; + private boolean useSnapshot; + + public SelectedTemplateInfo(TemplateInfo templateInfo, boolean useSnapshot) { + this.templateInfo = templateInfo; + this.useSnapshot = useSnapshot; + } + + public TemplateID id() { + return useSnapshot ? templateInfo.snapshotId() : templateInfo.id(); + } + + public TemplateInfo templateInfo() { + return templateInfo; + } + + public boolean useSnapshot() { + return useSnapshot; + } + + public void setUseSnapshot(boolean useSnapshot) { + this.useSnapshot = useSnapshot; + } + + @Override + public int hashCode() { + return Objects.hash(templateInfo, useSnapshot); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SelectedTemplateInfo other = (SelectedTemplateInfo) obj; + return Objects.equals(templateInfo, other.templateInfo) && useSnapshot == other.useSnapshot; + } + + @Override + public String toString() { + return "SelectedTemplateInfo [templateInfo=" + templateInfo + ", useSnapshot=" + useSnapshot + "]"; + } + + @Override + public int compareTo(SelectedTemplateInfo o) { + return templateInfo.compareTo(o.templateInfo); + } + } + public enum Action { skip, append, @@ -176,12 +238,13 @@ public List read(Parameters ps) { Attrs attrs = e.getValue(); TemplateID templateId = TemplateID.from(id); + TemplateID snapshotId = TemplateID.from(attrs.getOrDefault(SNAPSHOT, id.toString())); String name = attrs.getOrDefault(NAME, id.toString()); String description = attrs.getOrDefault(DESCRIPTION, ""); String require[] = toArray(attrs.get(REQUIRE)); String tags[] = toArray(attrs.get(TAG)); - templates.add(new TemplateInfo(templateId, name, description, require, tags)); + templates.add(new TemplateInfo(templateId, name, description, snapshotId, require, tags)); } return templates; } @@ -211,12 +274,12 @@ public List getAvailableTemplates() { */ public class TemplateUpdater implements AutoCloseable { private static final String TOOL_BND = "tool.bnd"; - final List templates; + final List templates; final File folder; final MultiMap updates = new MultiMap<>(); final List closeables = new ArrayList<>(); - TemplateUpdater(File folder, List templates) { + TemplateUpdater(File folder, List templates) { this.folder = folder; this.templates = templates; templates.forEach(templ -> { @@ -277,12 +340,16 @@ public Map> updaters() { return updates; } - List make(TemplateInfo template) { - Jar jar = getFiles(template.id() + List make(SelectedTemplateInfo selectedTemplate) { + + TemplateID id = selectedTemplate.id(); + TemplateInfo template = selectedTemplate.templateInfo(); + + Jar jar = getFiles(id .uri()); closeables.add(jar); - String prefix = fixup(template.id() + String prefix = fixup(id .path()); List updates = new ArrayList<>(); @@ -396,7 +463,7 @@ public void close() throws Exception { /** * Create a TemplateUpdater */ - public TemplateUpdater updater(File folder, List templates) { + public TemplateUpdater updater(File folder, List templates) { return new TemplateUpdater(folder, templates); } diff --git a/biz.aQute.bndlib/src/aQute/bnd/wstemplates/TemplateID.java b/biz.aQute.bndlib/src/aQute/bnd/wstemplates/TemplateID.java index fedbae6f36..a8f6d30dbe 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/wstemplates/TemplateID.java +++ b/biz.aQute.bndlib/src/aQute/bnd/wstemplates/TemplateID.java @@ -26,7 +26,7 @@ public record TemplateID(String organisation, String repository, String path, St g("path", set(lit("/"), SEGMENT_P)), opt(lit("/"))), opt(lit("#"), g("branch", REF_P)) // ); - final static URI ROOT = URI.create("https://github.com/bndtools/bndtools.workspace.min#master"); + final static URI ROOT = URI.create("https://github.com/bndtools/bndtools.workspace.min#HEAD"); @Override public int compareTo(TemplateID o) { @@ -62,20 +62,16 @@ public URI uri() { public String repoUrl() { String uri = this.other; if (uri == null) { - if (!path.startsWith("tree")) { - uri = "https://github.com/" + organisation + "/" + repository + "/tree/master/" + path; - } else { - uri = "https://github.com/" + organisation + "/" + repository + "/" + path; - } + uri = "https://github.com/" + organisation + "/" + repository + "/tree/" + ref + "/" + path; } return uri; } /** * Parse the id into a Template ID. The default is - * `bndtools/bndtools.workspace.min#master`. The missing fields are taken - * from this default. If the id does not match the pattern, it is assumed to - * be a URI. + * `bndtools/bndtools.workspace.min#HEAD`. The missing fields are taken from + * this default. If the id does not match the pattern, it is assumed to be a + * URI. * * @param id id or uri * @return a TemplateId @@ -89,7 +85,11 @@ public static TemplateID from(String id) { String path = vs.getOrDefault("path", ""); if (!path.isEmpty()) path = path.substring(1); - String branch = vs.getOrDefault("branch", "master"); + + // HEAD seems to be the universal name for the primary branch + // (regardless if named 'master', 'main' or else) + // see https://github.com/orgs/community/discussions/23213 + String branch = vs.getOrDefault("branch", "HEAD"); return new TemplateID(org, repo, path, branch, null); }) .orElse(new TemplateID(null, null, "", null, id)); diff --git a/biz.aQute.bndlib/test/aQute/bnd/wstemplates/TemplateFragmentsTest.java b/biz.aQute.bndlib/test/aQute/bnd/wstemplates/TemplateFragmentsTest.java index feca5e4811..1a8cc16b7f 100644 --- a/biz.aQute.bndlib/test/aQute/bnd/wstemplates/TemplateFragmentsTest.java +++ b/biz.aQute.bndlib/test/aQute/bnd/wstemplates/TemplateFragmentsTest.java @@ -14,6 +14,7 @@ import aQute.bnd.http.HttpClient; import aQute.bnd.osgi.Builder; import aQute.bnd.result.Result; +import aQute.bnd.wstemplates.FragmentTemplateEngine.SelectedTemplateInfo; import aQute.bnd.wstemplates.FragmentTemplateEngine.TemplateInfo; import aQute.bnd.wstemplates.FragmentTemplateEngine.TemplateUpdater; import aQute.bnd.wstemplates.FragmentTemplateEngine.Update; @@ -31,13 +32,13 @@ void testId() { assertThat(defaultId.organisation()).isEqualTo("bndtools"); assertThat(defaultId.repository()).isEqualTo("workspace"); assertThat(defaultId.path()).isEqualTo(""); - assertThat(defaultId.ref()).isEqualTo("master"); + assertThat(defaultId.ref()).isEqualTo("HEAD"); TemplateID withOrg = TemplateID.from("acme"); assertThat(withOrg.organisation()).isEqualTo("acme"); assertThat(withOrg.repository()).isEqualTo("workspace"); assertThat(withOrg.path()).isEqualTo(""); - assertThat(withOrg.ref()).isEqualTo("master"); + assertThat(withOrg.ref()).isEqualTo("HEAD"); TemplateID withOrgRef = TemplateID.from("acme#main"); assertThat(withOrgRef.organisation()).isEqualTo("acme"); @@ -49,7 +50,7 @@ void testId() { assertThat(withOrgRepo.organisation()).isEqualTo("acme"); assertThat(withOrgRepo.repository()).isEqualTo("template"); assertThat(withOrgRepo.path()).isEqualTo(""); - assertThat(withOrgRepo.ref()).isEqualTo("master"); + assertThat(withOrgRepo.ref()).isEqualTo("HEAD"); TemplateID withOrgRepoRef = TemplateID.from("acme/template#foo/bar"); assertThat(withOrgRepoRef.organisation()).isEqualTo("acme"); @@ -61,7 +62,7 @@ void testId() { assertThat(withOrgRepoPath.organisation()).isEqualTo("acme"); assertThat(withOrgRepoPath.repository()).isEqualTo("template"); assertThat(withOrgRepoPath.path()).isEqualTo("foo/bar"); - assertThat(withOrgRepoPath.ref()).isEqualTo("master"); + assertThat(withOrgRepoPath.ref()).isEqualTo("HEAD"); TemplateID withOrgRepoPathRef = TemplateID.from("acme/template/foo/bar/y#feature/bar/x"); assertThat(withOrgRepoPathRef.organisation()).isEqualTo("acme"); @@ -84,15 +85,19 @@ void testId() { // translates to the followingg templateID: // org/repo/tree/commitSHA/subfolder/workspace-template/ TemplateID commitSHAUrl = TemplateID - .from("org/repo/tree/commitSHA/subfolder/workspace-template"); + .from("org/repo/subfolder/workspace-template#commitSHA"); assertThat(commitSHAUrl.organisation()).isEqualTo("org"); assertThat(commitSHAUrl.repository()).isEqualTo("repo"); - assertThat(commitSHAUrl.path()).isEqualTo("tree/commitSHA/subfolder/workspace-template"); + assertThat(commitSHAUrl.path()).isEqualTo("subfolder/workspace-template"); assertThat(commitSHAUrl.repoUrl()) .isEqualTo("https://github.com/org/repo/tree/commitSHA/subfolder/workspace-template"); - TemplateID externalRepo = TemplateID.from("org/repo/subfolder/workspace-template"); - assertThat(externalRepo.repoUrl()) + TemplateID defaultHEADUrl = TemplateID.from("org/repo/subfolder/workspace-template"); + assertThat(defaultHEADUrl.repoUrl()) + .isEqualTo("https://github.com/org/repo/tree/HEAD/subfolder/workspace-template"); + + TemplateID defaultMasterUrl = TemplateID.from("org/repo/subfolder/workspace-template#master"); + assertThat(defaultMasterUrl.repoUrl()) .isEqualTo("https://github.com/org/repo/tree/master/subfolder/workspace-template"); } @@ -107,7 +112,7 @@ void testRemote() throws Exception { // use an archived repository Result> result = tfs.read("-workspace-templates " + a.toURI() + ";name=a;description=A," - + "bndtools/workspace-templates/gradle#567648ff425693b27b191bd38ace7c9c10539c2d;name=b;description=B"); + + "bndtools/workspace-templates/gradle#567648ff425693b27b191bd38ace7c9c10539c2d;name=b;description=B;snapshot=bndtools/workspace-templates/gradle"); assertThat(result.isOk()).describedAs(result.toString()) .isTrue(); @@ -117,7 +122,9 @@ void testRemote() throws Exception { assertThat(infos.remove(0) .name()).isEqualTo("a"); - TemplateUpdater updater = tfs.updater(wsDir, infos); + TemplateUpdater updater = tfs.updater(wsDir, infos.stream() + .map(ti -> new SelectedTemplateInfo(ti, false)) + .toList()); updater.commit(); assertThat(IO.getFile(wsDir, "cnf/build.bnd")).isFile(); @@ -152,7 +159,9 @@ void test() throws Exception { List infos = result.unwrap(); assertThat(infos).hasSize(2); - TemplateUpdater updater = tfs.updater(wsDir, infos); + TemplateUpdater updater = tfs.updater(wsDir, infos.stream() + .map(ti -> new SelectedTemplateInfo(ti, false)) + .toList()); updater.commit(); assertThat(IO.getFile(wsDir, "cnf/build.bnd")).isFile(); diff --git a/bndtools.core/src/bndtools/wizards/newworkspace/Model.java b/bndtools.core/src/bndtools/wizards/newworkspace/Model.java index b0db87e725..afd311dfca 100644 --- a/bndtools.core/src/bndtools/wizards/newworkspace/Model.java +++ b/bndtools.core/src/bndtools/wizards/newworkspace/Model.java @@ -23,7 +23,7 @@ import aQute.bnd.build.Workspace; import aQute.bnd.wstemplates.FragmentTemplateEngine; -import aQute.bnd.wstemplates.FragmentTemplateEngine.TemplateInfo; +import aQute.bnd.wstemplates.FragmentTemplateEngine.SelectedTemplateInfo; import aQute.bnd.wstemplates.FragmentTemplateEngine.TemplateUpdater; import aQute.lib.io.IO; @@ -46,8 +46,8 @@ enum NewWorkspaceType { boolean clean = false; boolean updateWorkspace = false; boolean switchWorkspace = true; - List templates = new ArrayList<>(); - List selectedTemplates = new ArrayList<>(); + List templates = new ArrayList<>(); + List selectedTemplates = new ArrayList<>(); Progress validatedUrl = Progress.init; String urlValidationError; String error; @@ -162,7 +162,7 @@ public void clean(boolean selection) { clean = selection; } - void selectedTemplates(List list) { + void selectedTemplates(List list) { selectedTemplates = list; } diff --git a/bndtools.core/src/bndtools/wizards/newworkspace/NewWorkspaceWizard.java b/bndtools.core/src/bndtools/wizards/newworkspace/NewWorkspaceWizard.java index c10a80cb8d..08ed26bada 100644 --- a/bndtools.core/src/bndtools/wizards/newworkspace/NewWorkspaceWizard.java +++ b/bndtools.core/src/bndtools/wizards/newworkspace/NewWorkspaceWizard.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.stream.Stream; +import org.bndtools.core.ui.icons.Icons; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CheckboxTableViewer; @@ -20,6 +21,7 @@ import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.program.Program; @@ -42,6 +44,7 @@ import aQute.bnd.header.Parameters; import aQute.bnd.result.Result; import aQute.bnd.wstemplates.FragmentTemplateEngine; +import aQute.bnd.wstemplates.FragmentTemplateEngine.SelectedTemplateInfo; import aQute.bnd.wstemplates.FragmentTemplateEngine.TemplateInfo; import aQute.bnd.wstemplates.FragmentTemplateEngine.TemplateUpdater; import bndtools.central.Central; @@ -51,6 +54,8 @@ * Create a new Workspace Wizard. */ public class NewWorkspaceWizard extends Wizard implements IImportWizard, INewWizard { + + static final String DEFAULT_INDEX = "https://raw.githubusercontent.com/bndtools/workspace-templates/master/index.bnd"; static final Logger log = LoggerFactory.getLogger(NewWorkspaceWizard.class); @@ -60,6 +65,9 @@ public class NewWorkspaceWizard extends Wizard implements IImportWizard, INewWiz final FragmentTemplateEngine templates; private ScrolledFormText txtDescription; + final static Image checked = Icons.image("checked", false); + final static Image unchecked = Icons.image("unchecked", false); + public NewWorkspaceWizard() throws Exception { setWindowTitle("Create New bnd Workspace"); Workspace workspace = Central.getWorkspace(); @@ -73,7 +81,10 @@ public NewWorkspaceWizard() throws Exception { Parameters p = workspace.getMergedParameters("-workspace-template"); templates.read(p) .forEach(templates::add); - ui.write(() -> model.templates = templates.getAvailableTemplates()); + ui.write(() -> model.templates = templates.getAvailableTemplates() + .stream() + .map(ti -> new SelectedTemplateInfo(ti, false)) + .toList()); } catch (Exception e) { log.error("failed to read default index {}", e, e); } @@ -152,9 +163,9 @@ public void doubleClick(DoubleClickEvent e) { // Handle double click event IStructuredSelection selection = (IStructuredSelection) e.getSelection(); Object el = selection.getFirstElement(); - if (el instanceof TemplateInfo ti) { + if (el instanceof SelectedTemplateInfo sti) { // Open URL in browser - Program.launch(ti.id() + Program.launch(sti.id() .repoUrl()); } } @@ -167,8 +178,9 @@ public void doubleClick(DoubleClickEvent e) { @Override public String getText(Object element) { - if (element instanceof TemplateInfo) { - return ((TemplateInfo) element).name(); + if (element instanceof SelectedTemplateInfo sti) { + return sti.templateInfo() + .name(); } return super.getText(element); } @@ -181,8 +193,9 @@ public String getText(Object element) { @Override public String getText(Object element) { - if (element instanceof TemplateInfo) { - return ((TemplateInfo) element).description(); + if (element instanceof SelectedTemplateInfo sti) { + return sti.templateInfo() + .description(); } return super.getText(element); } @@ -195,15 +208,15 @@ public String getText(Object element) { @Override public String getText(Object element) { - if (element instanceof TemplateInfo ti) { - if (ti.id() + if (element instanceof SelectedTemplateInfo sti) { + if (sti.id() .organisation() .equals("bndtools")) { return "bndtools (Official)"; } else { - return ti.id() + return sti.id() .organisation() + " (3rd Party)"; } } @@ -213,9 +226,35 @@ public String getText(Object element) { }); + TableViewerColumn useSnapshotColumn = new TableViewerColumn(selectedTemplates, SWT.NONE); + useSnapshotColumn.getColumn() + .setText("Version"); + useSnapshotColumn.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof SelectedTemplateInfo sti) { + if (sti.useSnapshot()) { + return "Use snapshot version"; + + } else { + return "Use default version"; + } + } + + return "default"; + } + + + + }); + + useSnapshotColumn.setEditingSupport(new SelectedTemplateInfoEditingSupport(selectedTemplates)); + tableLayout.addColumnData(new ColumnWeightData(1, 80, false)); tableLayout.addColumnData(new ColumnWeightData(10, 200, true)); - tableLayout.addColumnData(new ColumnWeightData(20, 300, true)); + tableLayout.addColumnData(new ColumnWeightData(20, 80, true)); + tableLayout.addColumnData(new ColumnWeightData(30, 100, true)); Button addButton = new Button(container, SWT.PUSH); addButton.setText("+"); @@ -224,7 +263,7 @@ public String getText(Object element) { txtDescription = new ScrolledFormText(container, SWT.V_SCROLL | SWT.H_SCROLL, false); FormText formText = new FormText(txtDescription, SWT.NO_FOCUS); txtDescription.setFormText(formText); - formText.setText("Double click for the author's Github-Repo.", false, false); + formText.setText("Double click to open Github-Repo at the version.", false, false); ui.u("location", model.location, UI.text(location) @@ -253,9 +292,9 @@ public String getText(Object element) { ui.update(); } - List toTemplates(Object[] selection) { + List toTemplates(Object[] selection) { return Stream.of(selection) - .map(o -> (TemplateInfo) o) + .map(o -> (SelectedTemplateInfo) o) .toList(); } @@ -283,7 +322,10 @@ void addTemplate() { } else { result.unwrap() .forEach(templates::add); - ui.write(() -> model.templates = templates.getAvailableTemplates()); + ui.write(() -> model.templates = templates.getAvailableTemplates() + .stream() + .map(ti -> new SelectedTemplateInfo(ti, false)) + .toList()); } } catch (Exception e) { ui.write(() -> model.error = "failed to add the index: " + e); diff --git a/bndtools.core/src/bndtools/wizards/newworkspace/SelectedTemplateInfoEditingSupport.java b/bndtools.core/src/bndtools/wizards/newworkspace/SelectedTemplateInfoEditingSupport.java new file mode 100644 index 0000000000..012627c0ae --- /dev/null +++ b/bndtools.core/src/bndtools/wizards/newworkspace/SelectedTemplateInfoEditingSupport.java @@ -0,0 +1,50 @@ +package bndtools.wizards.newworkspace; + +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ComboBoxCellEditor; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.TableViewer; + +import aQute.bnd.wstemplates.FragmentTemplateEngine.SelectedTemplateInfo; + +public class SelectedTemplateInfoEditingSupport extends EditingSupport { + + private final TableViewer viewer; + + public SelectedTemplateInfoEditingSupport(TableViewer viewer) { + super(viewer); + this.viewer = viewer; + } + + @Override + protected CellEditor getCellEditor(Object element) { + // return new CheckboxCellEditor(null, SWT.CHECK | SWT.READ_ONLY); + String[] versions = new String[] { + "default", "snapshot" + }; + return new ComboBoxCellEditor(viewer.getTable(), versions); + + } + + @Override + protected boolean canEdit(Object element) { + return true; + } + + @Override + protected Object getValue(Object element) { + SelectedTemplateInfo sti = (SelectedTemplateInfo) element; + return sti.useSnapshot() ? 1 : 0; + + } + + @Override + protected void setValue(Object element, Object value) { + if (((Integer) value) == 0) { + ((SelectedTemplateInfo) element).setUseSnapshot(false); + } else if (((Integer) value) == 1) { + ((SelectedTemplateInfo) element).setUseSnapshot(true); + } + viewer.update(element, null); + } +} \ No newline at end of file