package net.sf.eclipsefp.haskell.ui.internal.editors.cabal.forms.stanzas; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Vector; import net.sf.eclipsefp.haskell.buildwrapper.BuildWrapperPlugin; import net.sf.eclipsefp.haskell.buildwrapper.types.Component; import net.sf.eclipsefp.haskell.buildwrapper.types.Component.ComponentType; import net.sf.eclipsefp.haskell.core.HaskellCorePlugin; import net.sf.eclipsefp.haskell.core.cabalmodel.CabalSyntax; import net.sf.eclipsefp.haskell.core.cabalmodel.PackageDescription; import net.sf.eclipsefp.haskell.core.cabalmodel.PackageDescriptionLoader; import net.sf.eclipsefp.haskell.core.cabalmodel.PackageDescriptionStanza; import net.sf.eclipsefp.haskell.core.code.EHaskellCommentStyle; import net.sf.eclipsefp.haskell.core.code.ModuleCreationInfo; import net.sf.eclipsefp.haskell.core.code.SourceFileGenerator; import net.sf.eclipsefp.haskell.core.internal.code.CodeGenerator; import net.sf.eclipsefp.haskell.core.preferences.ICorePreferenceNames; import net.sf.eclipsefp.haskell.core.preferences.TemplateVariables; import net.sf.eclipsefp.haskell.ui.HaskellUIPlugin; import net.sf.eclipsefp.haskell.ui.internal.editors.cabal.CabalFormEditor; import net.sf.eclipsefp.haskell.ui.internal.editors.cabal.forms.CabalFormPage; import net.sf.eclipsefp.haskell.ui.internal.editors.cabal.forms.CabalFormSection; import net.sf.eclipsefp.haskell.ui.internal.util.UITexts; import net.sf.eclipsefp.haskell.util.LangUtil; import net.sf.eclipsefp.haskell.util.PlatformUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuCreator; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.IFormPart; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; /** * Page for handling test-suite stanzas in the Cabal file. * @author Alejandro Serrano, JP Moresmau * */ public class TestSuitesPage extends CabalFormPage implements SelectionListener { private List execsList; @SuppressWarnings ( "unused" ) private boolean ignoreModify = false; private CabalFormEditor formEditor; DependenciesSection depsSection; SourceDirsSection sourceDirsSection; ModulesTestSuiteSection modulesSection; TestTypeSection typeSection; String nextSelected = null; public TestSuitesPage( final FormEditor editor, final IProject project ) { super( editor, TestSuitesPage.class.getName(), UITexts.cabalEditor_testSuites, project ); } private enum TestType { HTF(ICorePreferenceNames.TEMPLATE_MODULE_HTF,ICorePreferenceNames.TEMPLATE_MAIN_HTF){ @Override public void addVariables( final Map<String, String> vars ,final String imports) { /** * imports */ vars.put( TemplateVariables.IMPORTS_HTF, imports ); /** * generate HTF module header */ if (vars.containsKey( TemplateVariables.MODULE )){ String mod1=HaskellCorePlugin.populateTemplate( ICorePreferenceNames.TEMPLATE_MODULE_HTF, vars ); vars.put( TemplateVariables.MODULE_HTF, mod1 ); } } }, TASTY(ICorePreferenceNames.TEMPLATE_MODULE_TASTY,ICorePreferenceNames.TEMPLATE_MAIN_TASTY){ @Override public void addVariables( final Map<String, String> vars ,final String imports) { /** * imports */ vars.put( TemplateVariables.IMPORTS, imports ); /** * generate HTF module header */ if (vars.containsKey( TemplateVariables.MODULE )){ String mod1=HaskellCorePlugin.populateTemplate( ICorePreferenceNames.TEMPLATE_MODULE_TASTY, vars ); vars.put( TemplateVariables.MODULE_TASTY, mod1 ); } } }, OTHER(ICorePreferenceNames.TEMPLATE_MODULE,ICorePreferenceNames.TEMPLATE_MAIN){ @Override public void addVariables( final Map<String, String> vars ,final String imports) { /** * imports */ vars.put( TemplateVariables.IMPORTS, imports ); } }; private String moduleTemplate; private String mainTemplate; private TestType(final String mod,final String main){ moduleTemplate=mod; mainTemplate=main; } /** * @return the moduleTemplate */ public String getModuleTemplate() { return moduleTemplate; } /** * @return the mainTemplate */ public String getMainTemplate() { return mainTemplate; } public abstract void addVariables( final Map<String, String> vars ,String imports); } private TestType getTestType(final String pref){ switch(pref){ case ICorePreferenceNames.TEMPLATE_CABAL_HTF: return TestType.HTF; case ICorePreferenceNames.TEMPLATE_CABAL_TASTY: return TestType.TASTY; default: return TestType.OTHER; } } public void addStanza(final PackageDescription desc,final String pref,final TestSuiteDialog.TestSuiteDef def,final boolean overwrite){ Map<String,String> vars=new HashMap<>(); vars.put( TemplateVariables.PROJECT_NAME, getPackageDescription().getPackageStanza().getName() ); vars.put( TemplateVariables.SRC, def.getSrc().getProjectRelativePath().toPortableString() ); vars.put( TemplateVariables.USER_NAME, PlatformUtil.getCurrentUser() ); vars.put( TemplateVariables.SECTION_NAME,def.getName()); String t=HaskellCorePlugin.populateTemplate( pref, vars ); PackageDescriptionStanza pd=PackageDescriptionLoader.loadStanza( t ); if (pd!=null){ Set<String> srcs=new HashSet<>(); boolean needLibrary=false; StringBuilder imports=new StringBuilder(); TestType testType=getTestType( pref ); /** * for each testes module, reference the module directly or just the library */ for (TestSuiteDialog.ModuleDef md:def.getModules()){ if (md.isLibrary()){ needLibrary=true; } else{ pd.addToPropertyList( CabalSyntax.FIELD_OTHER_MODULES, md.getModule() ); srcs.add( md.getSrcPath() ); } /** * create test module */ String m=createTestModule( pd, def, md.getModule(), testType.getModuleTemplate(),overwrite ); /** * generate import directive */ if (TestType.HTF.equals( testType )){ vars.put( TemplateVariables.MODULE_NAME,m); String mod1=HaskellCorePlugin.populateTemplate( ICorePreferenceNames.TEMPLATE_IMPORT_HTF, vars ); imports.append(mod1); } else { imports.append("import "+m+PlatformUtil.NL); } } srcs.add( def.getSrc().getProjectRelativePath().toPortableString()); /** * sources */ for (String src:srcs){ pd.addToPropertyList( CabalSyntax.FIELD_HS_SOURCE_DIRS, src); } /** * dependency on library */ if (needLibrary){ pd.addToPropertyList( CabalSyntax.FIELD_BUILD_DEPENDS, desc.getPackageStanza().getName() ); String value=desc.getPackageStanza().getProperties().get(CabalSyntax.FIELD_CABAL_VERSION); if (value!=null){ if (value.startsWith( ">=" )) { value=value.substring( 2 ).trim(); } if (value.length()>0 ){ try { double d=Double.parseDouble( value ); if (d<1.8){ desc.getPackageStanza().update( CabalSyntax.FIELD_CABAL_VERSION, ">=1.8" ); } } catch (NumberFormatException nfe){ HaskellUIPlugin.log( nfe ); } } } else { desc.getPackageStanza().update( CabalSyntax.FIELD_CABAL_VERSION, ">=1.8" ); } } /** * create main */ if (! ICorePreferenceNames.TEMPLATE_CABAL_DETAILED.equals( pref )){ createMain( pd, def ,testType,imports.toString(),overwrite); } desc.addStanza( pd ); } } /** * create test Main module * @param pd * @param def * @param isHTF * @param imports * @param overwrite should we overwrite existing files? */ private void createMain( final PackageDescriptionStanza pd,final TestSuiteDialog.TestSuiteDef def,final TestType testType,final String imports,final boolean overwrite){ final String mainName=def.getName() + "." + EHaskellCommentStyle.USUAL.getFileExtension(); //$NON-NLS-1$ pd.update( CabalSyntax.FIELD_MAIN_IS, mainName ); ModuleCreationInfo mci=new ModuleCreationInfo(); mci.setCommentStyle( EHaskellCommentStyle.USUAL ); mci.setModuleName( "Main" ); mci.setSourceContainer( def.getSrc() ); mci.setProject( sourceDirsSection.getProject() ); mci.setTemplatePreferenceName(testType.getMainTemplate()); CodeGenerator cg=new CodeGenerator(){ /* (non-Javadoc) * @see net.sf.eclipsefp.haskell.core.internal.code.CodeGenerator#addVariables(java.util.Map) */ @Override protected void addVariables( final Map<String, String> vars ) { testType.addVariables( vars, imports ); } }; SourceFileGenerator gen=new SourceFileGenerator(cg){ /* (non-Javadoc) * @see net.sf.eclipsefp.haskell.core.code.SourceFileGenerator#createFileName(net.sf.eclipsefp.haskell.core.code.EHaskellCommentStyle, java.lang.String) */ @Override protected String createFileName( final EHaskellCommentStyle style, final String moduleName ) { return mainName; } }; gen.setOverwrite( overwrite ); try { IFile f=gen.createFile( new NullProgressMonitor(), mci ); f.setPersistentProperty( BuildWrapperPlugin.EDITORSTANZA_PROPERTY, def.getName() ); } catch(CoreException ce){ HaskellUIPlugin.log( ce ); } } /** * create test module * @param pd * @param def * @param module the module to test * @param pref * @param overwrite should we overwrite existing files? * @return */ private String createTestModule(final PackageDescriptionStanza pd,final TestSuiteDialog.TestSuiteDef def,final String module,final String pref,final boolean overwrite){ String testModule=module+"Test"; pd.addToPropertyList( CabalSyntax.FIELD_OTHER_MODULES,testModule ); String[] mods=testModule.split( "\\." ); ModuleCreationInfo mci=new ModuleCreationInfo(); mci.setCommentStyle( EHaskellCommentStyle.USUAL ); /** * module name and parent folders */ mci.setModuleName( mods[mods.length-1] ); if (mods.length>1){ mci.setFolders( new Path(LangUtil.join( Arrays.asList( mods ).subList( 0, mods.length-1 ), "/" ))); } mci.setSourceContainer( def.getSrc() ); mci.setProject( sourceDirsSection.getProject() ); mci.setTemplatePreferenceName( pref); CodeGenerator cg=new CodeGenerator(){ /* (non-Javadoc) * @see net.sf.eclipsefp.haskell.core.internal.code.CodeGenerator#addVariables(java.util.Map) */ @Override protected void addVariables( final Map<String, String> vars ) { /** * import tested module */ vars.put(TemplateVariables.IMPORTS, "import "+module+PlatformUtil.NL ); } }; SourceFileGenerator gen=new SourceFileGenerator(cg); gen.setOverwrite( overwrite ); try { IFile f=gen.createFile( new NullProgressMonitor(), mci ); f.setPersistentProperty( BuildWrapperPlugin.EDITORSTANZA_PROPERTY, def.getName() ); } catch(CoreException ce){ HaskellUIPlugin.log( ce ); } return testModule; } /*public String createNewStdioStanza(final String name,final Map<String,String> extraVars) { return HaskellCorePlugin.populateTemplate( ICorePreferenceNames.TEMPLATE_CABAL_STDIO, extraVars ); // PackageDescriptionStanza stanza = desc.addStanza( // CabalSyntax.SECTION_TESTSUITE, name ); // stanza.setIndent( 2 ); // stanza.update( CabalSyntax.FIELD_TYPE, CabalSyntax.VALUE_EXITCODE_STDIO_1_0.getCabalName() ); // stanza.update( CabalSyntax.FIELD_BUILD_DEPENDS, "base >= 4" ); // stanza.update( CabalSyntax.FIELD_HS_SOURCE_DIRS, "src" ); // stanza.update( CabalSyntax.FIELD_GHC_OPTIONS, "-Wall -rtsopts" ); // return stanza; } public String createNewDetailedStanza(final String name,final Map<String,String> extraVars) { return HaskellCorePlugin.populateTemplate( ICorePreferenceNames.TEMPLATE_CABAL_DETAILED, extraVars ); } public String createNewTestFrameworkStanza(final String name,final Map<String,String> extraVars) { return HaskellCorePlugin.populateTemplate( ICorePreferenceNames.TEMPLATE_CABAL_TF, extraVars ); } */ public java.util.List<PackageDescriptionStanza> getStanzas( final PackageDescription description ) { return description.getTestSuiteStanzas(); } public ComponentType getComponentType() { return ComponentType.TESTSUITE; } @Override protected void createFormContent( final IManagedForm managedForm ) { ScrolledForm form = managedForm.getForm(); FormToolkit toolkit = managedForm.getToolkit(); toolkit.decorateFormHeading( form.getForm() ); form.updateToolBar(); form.setText( UITexts.cabalEditor_testSuites ); form.getBody().setLayout( createUnequalGridLayout( 2, 6, 12 ) ); Composite left = toolkit.createComposite( form.getBody() ); left.setLayout( new GridLayout( 1, true ) ); left.setLayoutData( new GridData( GridData.FILL_BOTH ) ); ToolBar tbar = new ToolBar( left, SWT.NULL ); toolkit.adapt( tbar, true, true ); GridData tbarGD = new GridData( GridData.FILL_HORIZONTAL ); tbarGD.grabExcessHorizontalSpace = true; tbar.setLayoutData( tbarGD ); ToolBarManager manager = new ToolBarManager( tbar ); execsList = new List( left, SWT.SINGLE | SWT.BORDER ); GridData listGD = new GridData( GridData.FILL_BOTH ); listGD.grabExcessHorizontalSpace = true; listGD.grabExcessVerticalSpace = true; listGD.widthHint = 150; execsList.setLayoutData( listGD ); execsList.addSelectionListener( this ); execsList.setData( FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER ); toolkit.adapt( execsList, true, true ); Action addAction = new Action( UITexts.cabalEditor_add, IAction.AS_DROP_DOWN_MENU ) { // Nothing here }; addAction.setImageDescriptor( PlatformUI.getWorkbench().getSharedImages() .getImageDescriptor( ISharedImages.IMG_OBJ_ADD ) ); final Action addHTFAction = new NewTestSuiteAction( UITexts.cabalEditor_HTFTestSuite, ICorePreferenceNames.TEMPLATE_CABAL_HTF ) ; final Action addTastyAction = new NewTestSuiteAction( UITexts.cabalEditor_TastyTestSuite, ICorePreferenceNames.TEMPLATE_CABAL_TASTY ) ; final Action addTestFrameworkAction = new NewTestSuiteAction( UITexts.cabalEditor_testFrameworkTestSuite, ICorePreferenceNames.TEMPLATE_CABAL_TF ) ; final Action addStdioAction = new NewTestSuiteAction( UITexts.cabalEditor_stdioTestSuite,ICorePreferenceNames.TEMPLATE_CABAL_STDIO ); final Action addDetailedAction = new NewTestSuiteAction( UITexts.cabalEditor_detailedTestSuite, ICorePreferenceNames.TEMPLATE_CABAL_DETAILED ) ; addAction.setMenuCreator( new IMenuCreator() { Menu menu; MenuManager menuManager; @Override public Menu getMenu( final Menu parent ) { // Not expected return null; } @Override public Menu getMenu( final Control parent ) { menuManager = new MenuManager(); menu = menuManager.createContextMenu( parent ); menuManager.add( addHTFAction ); menuManager.add( addTastyAction ); menuManager.add( addTestFrameworkAction ); menuManager.add( addStdioAction ); menuManager.add( addDetailedAction ); menuManager.update( true ); return menu; } @Override public void dispose() { menuManager.dispose(); } } ); Action removeAction = new Action( UITexts.cabalEditor_remove, IAction.AS_PUSH_BUTTON ) { @Override public void run() { PackageDescription lastDescription = formEditor.getPackageDescription(); PackageDescriptionStanza stanza = sourceDirsSection.getStanza(); lastDescription.removeStanza( stanza ); formEditor.getModel().set( lastDescription.dump() ); } }; removeAction.setImageDescriptor( PlatformUI.getWorkbench() .getSharedImages().getImageDescriptor( ISharedImages.IMG_TOOL_DELETE ) ); manager.add( addAction ); manager.add( removeAction ); manager.update( true ); Composite right = toolkit.createComposite( form.getBody() ); right.setLayout( createGridLayout( 3, 0, 0 ) ); right.setLayoutData( new GridData( GridData.FILL_BOTH ) ); formEditor = ( CabalFormEditor )getEditor(); depsSection = new DependenciesSection( this, right, formEditor, project ); managedForm.addPart( depsSection ); GridData depsGD = new GridData(GridData.FILL_BOTH); depsGD.verticalSpan = 3; depsGD.grabExcessVerticalSpace = true; depsSection.getSection().setLayoutData( depsGD ); modulesSection = new ModulesTestSuiteSection( this, right, formEditor, project ); managedForm.addPart( modulesSection ); GridData modulesGD = new GridData(GridData.FILL_BOTH); modulesGD.verticalSpan = 3; modulesGD.grabExcessVerticalSpace = true; modulesSection.getSection().setLayoutData( modulesGD ); typeSection = new TestTypeSection( this, right, formEditor, project ); managedForm.addPart( typeSection ); sourceDirsSection = new SourceDirsSection( this, right, formEditor, project ); managedForm.addPart( sourceDirsSection ); managedForm.addPart( new CompilerOptionsSection( this, right, formEditor, project ) ); setAllEditable( false ); toolkit.paintBordersFor( form ); this.finishedLoading(); } void setAllEditable(final boolean editable) { for( IFormPart p: getManagedForm().getParts() ) { if( p instanceof CabalFormSection ) { ( ( CabalFormSection )p ).setAllEditable( editable ); } } } void setStanza(final PackageDescriptionStanza stanza,final boolean first) { for( IFormPart p: getManagedForm().getParts() ) { if( p instanceof CabalFormSection ) { ( ( CabalFormSection )p ).setStanza( stanza, first ); } } } @Override public void widgetSelected( final SelectionEvent e ) { ignoreModify = true; PackageDescriptionStanza stanza; if (execsList.getSelectionCount() == 0) { stanza = null; } else { String name = execsList.getSelection()[0]; stanza = getPackageDescription().getComponentStanza( new Component( getComponentType(), name, "", true ) ); } modulesSection.refreshInput( project, this.getPackageDescription(), stanza, true ); setStanza(stanza,true); ignoreModify = false; } public void selectStanza(final PackageDescriptionStanza stanza){ if (execsList.getItemCount()==0){ nextSelected=stanza.getName(); } else { execsList.setSelection( new String[]{stanza.getName()} ); } } @Override protected void setPackageDescriptionInternal( final PackageDescription packageDescription ) { ignoreModify = true; java.util.List<String> inList = Arrays.asList( execsList.getItems() ); Vector<String> inPackage = new Vector<>(); for (PackageDescriptionStanza execStanza : this.getStanzas( packageDescription )) { inPackage.add( execStanza.getName() ); } for (String listElement : inList) { if (inPackage.indexOf( listElement ) == -1) { execsList.remove( listElement ); } } for (String pkgElement : inPackage) { if (inList.indexOf( pkgElement ) == -1) { execsList.add( pkgElement ); } } if (nextSelected != null) { execsList.setSelection( new String[] { nextSelected } ); nextSelected = null; } widgetSelected( null ); ignoreModify = false; } @Override public void widgetDefaultSelected( final SelectionEvent e ) { // Do nothing } private class NewTestSuiteAction extends Action { private final String pref; public NewTestSuiteAction( final String text,final String pref) { super( text, IAction.AS_PUSH_BUTTON ); this.pref=pref; } @Override public void run() { PackageDescription lastDescription = formEditor.getPackageDescription(); IProject p=sourceDirsSection.getProject(); Set<String> names=new HashSet<>(Arrays.asList(execsList.getItems())); TestSuiteDialog dialog = new TestSuiteDialog( execsList.getShell(),p,lastDescription,names); if (dialog.open() == Window.OK) { TestSuiteDialog.TestSuiteDef def = dialog.getDefinition(); //createNewTestFrameworkStanza( lastDescription, execName ); addStanza( lastDescription, this.pref, def ,dialog.isOverwrite()); nextSelected = def.getName(); formEditor.getModel().set( lastDescription.dump() ); } } } }