diff options
216 files changed, 9601 insertions, 1171 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_bubble_chart.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_bubble_chart.html.erb index af427c247c6..dfb2fba1b93 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_bubble_chart.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_bubble_chart.html.erb @@ -11,7 +11,8 @@ maxItems = widget_properties["maxItems"].to_i filter = MeasureFilter.find_by_id(filterId.to_i) - @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} + if filter + @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} %> <div class="bubble-chart-widget" id="<%= containerId %>"> @@ -62,9 +63,6 @@ })(); </script> <!--<![endif]--> - - - - - - +<% else %> + <p><%= image_tag 'warning.png' %> <%= message 'measure_filter.widget.unknown_filter_warning' -%></p> +<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_cloud.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_cloud.html.erb index 7d967faf8ec..d7de32d63c3 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_cloud.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_cloud.html.erb @@ -5,7 +5,8 @@ maxItems = widget_properties['maxItems'].to_i filter = MeasureFilter.find_by_id(filterId.to_i) - @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} + if filter + @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} %> <div class="histogram-widget" id="<%= containerId %>"> @@ -53,7 +54,6 @@ })(); </script> <!--<![endif]--> - - - - +<% else %> + <p><%= image_tag 'warning.png' %> <%= message 'measure_filter.widget.unknown_filter_warning' -%></p> +<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_histogram.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_histogram.html.erb index 000ada35ae2..c18ce87276a 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_histogram.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_histogram.html.erb @@ -7,7 +7,8 @@ relativeScale = widget_properties["relativeScale"] filter = MeasureFilter.find_by_id(filterId.to_i) - @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} + if filter + @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} %> <div class="histogram-widget" id="<%= containerId %>"> @@ -56,6 +57,6 @@ })(); </script> <!--<![endif]--> - - - +<% else %> + <p><%= image_tag 'warning.png' %> <%= message 'measure_filter.widget.unknown_filter_warning' -%></p> +<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_pie_chart.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_pie_chart.html.erb index d7d2591b1ca..ca7af9c3d18 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_pie_chart.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_pie_chart.html.erb @@ -6,7 +6,8 @@ maxItems = widget_properties["maxItems"].to_i filter = MeasureFilter.find_by_id(filterId.to_i) - @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} + if filter + @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} %> <div class="pie-chart-widget" id="<%= containerId %>"> @@ -58,6 +59,6 @@ })(); </script> <!--<![endif]--> - - - +<% else %> + <p><%= image_tag 'warning.png' %> <%= message 'measure_filter.widget.unknown_filter_warning' -%></p> +<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_treemap.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_treemap.html.erb index 53911880335..771dc43a823 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_treemap.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/measures/measure_filter_treemap.html.erb @@ -5,7 +5,8 @@ maxItems = widget_properties['maxItems'].to_i filter = MeasureFilter.find_by_id(filterId.to_i) - @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} + if filter + @widget_title = link_to h(filter.name), {:controller => 'measures', :action => 'filter', :id => filter.id, :display => 'list'} %> <div class="treemap-widget" id="<%= containerId %>"> @@ -56,3 +57,6 @@ })(); </script> <!--<![endif]--> +<% else %> + <p><%= image_tag 'warning.png' %> <%= message 'measure_filter.widget.unknown_filter_warning' -%></p> +<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb index 7ee4fb0e229..e68012f744b 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb @@ -32,13 +32,13 @@ <% if ncloc_language_dist_hash %> <% if ncloc_language_dist_hash.size > 1 %> - <table class="data widget-barchar"> + <table class="data widget-barchar" id="size-widget-language-dist"> <% - max = ncloc_language_dist_hash.max_by{|_k,v| v.to_i}[1].to_i + max = Math.sqrt(ncloc_language_dist_hash.max_by{|_k,v| v.to_i}[1].to_i) - # Sort lines language distribution by language name + # Sort lines language distribution by lines count languages_by_key = Hash[languages.collect { |l| [l.getKey(), l.getName] }] - ncloc_language_dist_hash.sort {|v1,v2| (languages_by_key[v1[0]] ? languages_by_key[v1[0]].to_s : v1[0]) <=> (languages_by_key[v2[0]] ? languages_by_key[v2[0]].to_s : v2[0]) }.each do |language_key, language_ncloc| + ncloc_language_dist_hash.sort {|v1,v2| v2[1].to_i <=> v1[1].to_i }.each do |language_key, language_ncloc| %> <tr> <td> @@ -48,12 +48,23 @@ <td class="thin right nowrap"> <%= ncloc.format_numeric_value(language_ncloc) %> </td> - <td> - <%= barchart(:width => 70, :percent => (40 * language_ncloc.to_i / max).to_i) %> - </td> </tr> <% end %> </table> + <script> + jQuery(function () { + var chart = jQuery('#size-widget-language-dist'), + count = chart.find('tr').length; + if (count > 3) { + chart.find('tr:gt(2)').hide(); + var moreLinkBox = jQuery('<div></div>').addClass('widget-barchart-more').insertAfter(chart), + moreLink = jQuery('<a><%= message('more') -%></a>').appendTo(moreLinkBox).on('click', function () { + jQuery(this).hide(); + chart.find('tr').show(); + }); + } + }); + </script> <% else %> <% language_key = ncloc_language_dist_hash.first[0] diff --git a/plugins/sonar-cpd-plugin/pom.xml b/plugins/sonar-cpd-plugin/pom.xml index 2903ae5a4be..334122c4b91 100644 --- a/plugins/sonar-cpd-plugin/pom.xml +++ b/plugins/sonar-cpd-plugin/pom.xml @@ -54,6 +54,11 @@ <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.codehaus.sonar.plugins</groupId> + <artifactId>sonar-xoo-plugin</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java index 7548282c87b..2f2a7633f40 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java @@ -21,14 +21,13 @@ package org.sonar.plugins.cpd; import org.slf4j.Logger; import org.sonar.api.BatchExtension; -import org.sonar.api.batch.SensorContext; -import org.sonar.api.resources.Project; +import org.sonar.api.batch.sensor.SensorContext; public abstract class CpdEngine implements BatchExtension { abstract boolean isLanguageSupported(String language); - abstract void analyse(Project project, String language, SensorContext context); + abstract void analyse(String language, SensorContext context); protected void logExclusions(String[] exclusions, Logger logger) { if (exclusions.length > 0) { diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java new file mode 100644 index 00000000000..3f0f9d83b8c --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java @@ -0,0 +1,51 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.cpd; + +import org.sonar.api.BatchComponent; +import org.sonar.api.batch.CpdMapping; + +import javax.annotation.CheckForNull; + +public class CpdMappings implements BatchComponent { + + private final CpdMapping[] mappings; + + public CpdMappings(CpdMapping[] mappings) { + this.mappings = mappings; + } + + public CpdMappings() { + this(new CpdMapping[0]); + } + + @CheckForNull + public CpdMapping getMapping(String language) { + if (mappings != null) { + for (CpdMapping cpdMapping : mappings) { + if (cpdMapping.getLanguage().getKey().equals(language)) { + return cpdMapping; + } + } + } + return null; + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java index 5f9df9621e1..defa9fe260e 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java @@ -35,7 +35,7 @@ public final class CpdPlugin extends SonarPlugin { public List getExtensions() { return ImmutableList.of( - PropertyDefinition.builder(CoreProperties.CPD_CROSS_RPOJECT) + PropertyDefinition.builder(CoreProperties.CPD_CROSS_PROJECT) .defaultValue(CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE + "") .name("Cross project duplication detection") .description("SonarQube supports the detection of cross project duplications. Activating this property will slightly increase each SonarQube analysis time.") @@ -65,11 +65,12 @@ public final class CpdPlugin extends SonarPlugin { .build(), CpdSensor.class, + CpdMappings.class, SumDuplicationsDecorator.class, DuplicationDensityDecorator.class, IndexFactory.class, - SonarEngine.class, - SonarBridgeEngine.class); + JavaCpdEngine.class, + DefaultCpdEngine.class); } } diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java index 53a45416cb7..dfeabd65635 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java @@ -23,12 +23,14 @@ import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; -import org.sonar.api.batch.Sensor; -import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.Phase; import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.config.Settings; -import org.sonar.api.resources.Project; +@Phase(name = Phase.Name.POST) public class CpdSensor implements Sensor { private static final Logger LOG = LoggerFactory.getLogger(CpdSensor.class); @@ -38,15 +40,17 @@ public class CpdSensor implements Sensor { private Settings settings; private FileSystem fs; - public CpdSensor(SonarEngine sonarEngine, SonarBridgeEngine sonarBridgeEngine, Settings settings, FileSystem fs) { + public CpdSensor(JavaCpdEngine sonarEngine, DefaultCpdEngine sonarBridgeEngine, Settings settings, FileSystem fs) { this.sonarEngine = sonarEngine; this.sonarBridgeEngine = sonarBridgeEngine; this.settings = settings; this.fs = fs; } - public boolean shouldExecuteOnProject(Project project) { - return true; + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("CPD Sensor"); + } @VisibleForTesting @@ -66,7 +70,8 @@ public class CpdSensor implements Sensor { return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY); } - public void analyse(Project project, SensorContext context) { + @Override + public void execute(SensorContext context) { for (String language : fs.languages()) { if (isSkipped(language)) { LOG.info("Detection of duplicated code is skipped for {}", language); @@ -79,13 +84,8 @@ public class CpdSensor implements Sensor { continue; } LOG.info("{} is used for {}", engine, language); - engine.analyse(project, language, context); + engine.analyse(language, context); } } - @Override - public String toString() { - return getClass().getSimpleName(); - } - } diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java index df4bc036115..33558ec898a 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java @@ -27,22 +27,25 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.batch.CpdMapping; -import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; +import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; import org.sonar.api.utils.SonarException; +import org.sonar.batch.duplication.BlockCache; import org.sonar.duplications.DuplicationPredicates; import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.FileBlocks; import org.sonar.duplications.index.CloneGroup; import org.sonar.duplications.internal.pmd.TokenizerBridge; import org.sonar.plugins.cpd.index.IndexFactory; import org.sonar.plugins.cpd.index.SonarDuplicationsIndex; -import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutionException; @@ -51,9 +54,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -public class SonarBridgeEngine extends CpdEngine { +public class DefaultCpdEngine extends CpdEngine { - private static final Logger LOG = LoggerFactory.getLogger(SonarBridgeEngine.class); + private static final Logger LOG = LoggerFactory.getLogger(DefaultCpdEngine.class); /** * Limit of time to analyse one file (in seconds). @@ -61,28 +64,32 @@ public class SonarBridgeEngine extends CpdEngine { private static final int TIMEOUT = 5 * 60; private final IndexFactory indexFactory; - private final CpdMapping[] mappings; + private final CpdMappings mappings; private final FileSystem fs; private final Settings settings; + private final BlockCache duplicationCache; + private final Project project; - public SonarBridgeEngine(IndexFactory indexFactory, CpdMapping[] mappings, FileSystem fs, Settings settings) { + public DefaultCpdEngine(@Nullable Project project, IndexFactory indexFactory, CpdMappings mappings, FileSystem fs, Settings settings, BlockCache duplicationCache) { + this.project = project; this.indexFactory = indexFactory; this.mappings = mappings; this.fs = fs; this.settings = settings; + this.duplicationCache = duplicationCache; } - public SonarBridgeEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) { - this(indexFactory, new CpdMapping[0], fs, settings); + public DefaultCpdEngine(IndexFactory indexFactory, CpdMappings mappings, FileSystem fs, Settings settings, BlockCache duplicationCache) { + this(null, indexFactory, mappings, fs, settings, duplicationCache); } @Override public boolean isLanguageSupported(String language) { - return getMapping(language) != null; + return true; } @Override - public void analyse(Project project, String languageKey, SensorContext context) { + public void analyse(String languageKey, SensorContext context) { String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); logExclusions(cpdExclusions, LOG); FilePredicates p = fs.predicates(); @@ -90,29 +97,34 @@ public class SonarBridgeEngine extends CpdEngine { p.hasType(InputFile.Type.MAIN), p.hasLanguage(languageKey), p.doesNotMatchPathPatterns(cpdExclusions) - ))); + ))); if (sourceFiles.isEmpty()) { return; } - CpdMapping mapping = getMapping(languageKey); - if (mapping == null) { - return; - } + CpdMapping mapping = mappings.getMapping(languageKey); // Create index SonarDuplicationsIndex index = indexFactory.create(project, languageKey); - TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(project, languageKey)); + TokenizerBridge bridge = null; + if (mapping != null) { + bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey)); + } for (InputFile inputFile : sourceFiles) { LOG.debug("Populating index from {}", inputFile); String resourceEffectiveKey = ((DeprecatedDefaultInputFile) inputFile).key(); - List<Block> blocks = bridge.chunk(resourceEffectiveKey, inputFile.file()); - index.insert(inputFile, blocks); + FileBlocks fileBlocks = duplicationCache.byComponent(resourceEffectiveKey); + if (fileBlocks != null) { + index.insert(inputFile, fileBlocks.blocks()); + } else if (bridge != null) { + List<Block> blocks2 = bridge.chunk(resourceEffectiveKey, inputFile.file()); + index.insert(inputFile, blocks2); + } } // Detect - Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(project, languageKey)); + Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(languageKey)); ExecutorService executorService = Executors.newSingleThreadExecutor(); try { @@ -123,7 +135,7 @@ public class SonarBridgeEngine extends CpdEngine { Iterable<CloneGroup> filtered; try { - List<CloneGroup> duplications = executorService.submit(new SonarEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS); + List<CloneGroup> duplications = executorService.submit(new JavaCpdEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS); filtered = Iterables.filter(duplications, minimumTokensPredicate); } catch (TimeoutException e) { filtered = null; @@ -134,7 +146,7 @@ public class SonarBridgeEngine extends CpdEngine { throw new SonarException("Fail during detection of duplication for " + inputFile, e); } - SonarEngine.save(context, inputFile, filtered); + JavaCpdEngine.save(context, inputFile, filtered); } } finally { executorService.shutdown(); @@ -142,7 +154,7 @@ public class SonarBridgeEngine extends CpdEngine { } @VisibleForTesting - int getBlockSize(Project project, String languageKey) { + int getBlockSize(String languageKey) { int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); if (blockSize == 0) { blockSize = getDefaultBlockSize(languageKey); @@ -162,7 +174,7 @@ public class SonarBridgeEngine extends CpdEngine { } @VisibleForTesting - int getMinimumTokens(Project project, String languageKey) { + int getMinimumTokens(String languageKey) { int minimumTokens = settings.getInt("sonar.cpd." + languageKey + ".minimumTokens"); if (minimumTokens == 0) { minimumTokens = settings.getInt(CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY); @@ -174,16 +186,4 @@ public class SonarBridgeEngine extends CpdEngine { return minimumTokens; } - @CheckForNull - private CpdMapping getMapping(String language) { - if (mappings != null) { - for (CpdMapping cpdMapping : mappings) { - if (cpdMapping.getLanguage().getKey().equals(language)) { - return cpdMapping; - } - } - } - return null; - } - } diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java index 130206427fc..2ea908dbb27 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java @@ -23,21 +23,20 @@ package org.sonar.plugins.cpd; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; -import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.duplication.DuplicationBuilder; import org.sonar.api.config.Settings; import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.PersistenceMode; import org.sonar.api.resources.Project; import org.sonar.api.utils.SonarException; +import org.sonar.batch.duplication.DefaultDuplicationBuilder; import org.sonar.duplications.block.Block; import org.sonar.duplications.block.BlockChunker; import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm; @@ -53,6 +52,7 @@ import org.sonar.plugins.cpd.index.IndexFactory; import org.sonar.plugins.cpd.index.SonarDuplicationsIndex; import javax.annotation.Nullable; + import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; @@ -61,11 +61,16 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -public class SonarEngine extends CpdEngine { +public class JavaCpdEngine extends CpdEngine { - private static final Logger LOG = LoggerFactory.getLogger(SonarEngine.class); + private static final Logger LOG = LoggerFactory.getLogger(JavaCpdEngine.class); private static final int BLOCK_SIZE = 10; @@ -77,20 +82,26 @@ public class SonarEngine extends CpdEngine { private final IndexFactory indexFactory; private final FileSystem fs; private final Settings settings; + private final Project project; - public SonarEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) { + public JavaCpdEngine(@Nullable Project project, IndexFactory indexFactory, FileSystem fs, Settings settings) { + this.project = project; this.indexFactory = indexFactory; this.fs = fs; this.settings = settings; } + public JavaCpdEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) { + this(null, indexFactory, fs, settings); + } + @Override public boolean isLanguageSupported(String language) { return "java".equals(language); } @Override - public void analyse(Project project, String languageKey, SensorContext context) { + public void analyse(String languageKey, SensorContext context) { String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); logExclusions(cpdExclusions, LOG); FilePredicates p = fs.predicates(); @@ -98,7 +109,7 @@ public class SonarEngine extends CpdEngine { p.hasType(InputFile.Type.MAIN), p.hasLanguage(languageKey), p.doesNotMatchPathPatterns(cpdExclusions) - ))); + ))); if (sourceFiles.isEmpty()) { return; } @@ -106,7 +117,7 @@ public class SonarEngine extends CpdEngine { detect(index, context, sourceFiles); } - private SonarDuplicationsIndex createIndex(Project project, String language, Iterable<InputFile> sourceFiles) { + private SonarDuplicationsIndex createIndex(@Nullable Project project, String language, Iterable<InputFile> sourceFiles) { final SonarDuplicationsIndex index = indexFactory.create(project, language); TokenChunker tokenChunker = JavaTokenProducer.build(); @@ -136,7 +147,7 @@ public class SonarEngine extends CpdEngine { return index; } - private void detect(SonarDuplicationsIndex index, SensorContext context, List<InputFile> sourceFiles) { + private void detect(SonarDuplicationsIndex index, org.sonar.api.batch.sensor.SensorContext context, List<InputFile> sourceFiles) { ExecutorService executorService = Executors.newSingleThreadExecutor(); try { for (InputFile inputFile : sourceFiles) { @@ -178,13 +189,13 @@ public class SonarEngine extends CpdEngine { } } - static void save(SensorContext context, InputFile inputFile, @Nullable Iterable<CloneGroup> duplications) { + static void save(org.sonar.api.batch.sensor.SensorContext context, InputFile inputFile, @Nullable Iterable<CloneGroup> duplications) { if (duplications == null || Iterables.isEmpty(duplications)) { return; } // Calculate number of lines and blocks Set<Integer> duplicatedLines = new HashSet<Integer>(); - double duplicatedBlocks = 0; + int duplicatedBlocks = 0; for (CloneGroup clone : duplications) { ClonePart origin = clone.getOriginPart(); for (ClonePart part : clone.getCloneParts()) { @@ -197,30 +208,32 @@ public class SonarEngine extends CpdEngine { } } // Save - context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1.0); - context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size()); - context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks); - - Measure data = new Measure(CoreMetrics.DUPLICATIONS_DATA, toXml(duplications)) - .setPersistenceMode(PersistenceMode.DATABASE); - context.saveMeasure(inputFile, data); - } - - private static String toXml(Iterable<CloneGroup> duplications) { - StringBuilder xml = new StringBuilder(); - xml.append("<duplications>"); + context.addMeasure(context.<Integer>measureBuilder() + .forMetric(CoreMetrics.DUPLICATED_FILES) + .onFile(inputFile) + .withValue(1) + .build()); + context.addMeasure(context.<Integer>measureBuilder() + .forMetric(CoreMetrics.DUPLICATED_LINES) + .onFile(inputFile) + .withValue(duplicatedLines.size()) + .build()); + context.addMeasure(context.<Integer>measureBuilder() + .forMetric(CoreMetrics.DUPLICATED_BLOCKS) + .onFile(inputFile) + .withValue(duplicatedBlocks) + .build()); + + DuplicationBuilder builder = context.duplicationBuilder(inputFile); for (CloneGroup duplication : duplications) { - xml.append("<g>"); + builder.originBlock(duplication.getOriginPart().getStartLine(), duplication.getOriginPart().getEndLine()); for (ClonePart part : duplication.getCloneParts()) { - xml.append("<b s=\"").append(part.getStartLine()) - .append("\" l=\"").append(part.getLines()) - .append("\" r=\"").append(StringEscapeUtils.escapeXml(part.getResourceId())) - .append("\"/>"); + if (!part.equals(duplication.getOriginPart())) { + ((DefaultDuplicationBuilder) builder).isDuplicatedBy(part.getResourceId(), part.getStartLine(), part.getEndLine()); + } } - xml.append("</g>"); } - xml.append("</duplications>"); - return xml.toString(); + builder.done(); } } diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java index f6f8981f6c1..6d5a7f1201d 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java @@ -23,29 +23,41 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.BatchExtension; +import org.sonar.api.BatchComponent; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; +import org.sonar.batch.bootstrap.AnalysisMode; import org.sonar.batch.index.ResourcePersister; import org.sonar.core.duplication.DuplicationDao; -public class IndexFactory implements BatchExtension { +import javax.annotation.Nullable; + +public class IndexFactory implements BatchComponent { private static final Logger LOG = LoggerFactory.getLogger(IndexFactory.class); private final Settings settings; private final ResourcePersister resourcePersister; private final DuplicationDao dao; + private final AnalysisMode mode; - public IndexFactory(Settings settings, ResourcePersister resourcePersister, DuplicationDao dao) { + public IndexFactory(AnalysisMode mode, Settings settings, @Nullable ResourcePersister resourcePersister, @Nullable DuplicationDao dao) { + this.mode = mode; this.settings = settings; this.resourcePersister = resourcePersister; this.dao = dao; } - public SonarDuplicationsIndex create(Project project, String languageKey) { - if (verifyCrossProject(project, LOG)) { + /** + * Used by new sensor mode + */ + public IndexFactory(AnalysisMode mode, Settings settings) { + this(mode, settings, null, null); + } + + public SonarDuplicationsIndex create(@Nullable Project project, String languageKey) { + if (verifyCrossProject(project, LOG) && dao != null && resourcePersister != null) { return new SonarDuplicationsIndex(new DbDuplicationsIndex(resourcePersister, project, dao, languageKey)); } return new SonarDuplicationsIndex(); @@ -55,11 +67,14 @@ public class IndexFactory implements BatchExtension { boolean verifyCrossProject(Project project, Logger logger) { boolean crossProject = false; - if (settings.getBoolean(CoreProperties.CPD_CROSS_RPOJECT)) { - if (settings.getBoolean(CoreProperties.DRY_RUN)) { - logger.info("Cross-project analysis disabled. Not supported on dry runs."); - } else if (StringUtils.isNotBlank(project.getBranch())) { + if (settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT)) { + if (mode.isPreview()) { + logger.info("Cross-project analysis disabled. Not supported in preview mode."); + } else if (StringUtils.isNotBlank(settings.getString(CoreProperties.PROJECT_BRANCH_PROPERTY))) { logger.info("Cross-project analysis disabled. Not supported on project branches."); + } else if (project == null) { + // New sensor mode + logger.info("Cross-project analysis disabled. Not supported in new sensor mode."); } else { logger.info("Cross-project analysis enabled"); crossProject = true; diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java index 74cb7f5fa5b..7d1bbeb4a5c 100644 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java @@ -27,6 +27,6 @@ public class CpdPluginTest { @Test public void getExtensions() { - assertThat(new CpdPlugin().getExtensions()).hasSize(9); + assertThat(new CpdPlugin().getExtensions()).hasSize(10); } } diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java index 89128b77a8a..7724fa6f725 100644 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java @@ -25,6 +25,7 @@ import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.resources.Java; +import org.sonar.batch.duplication.BlockCache; import org.sonar.plugins.cpd.index.IndexFactory; import static org.fest.assertions.Assertions.assertThat; @@ -32,16 +33,16 @@ import static org.mockito.Mockito.mock; public class CpdSensorTest { - SonarEngine sonarEngine; - SonarBridgeEngine sonarBridgeEngine; + JavaCpdEngine sonarEngine; + DefaultCpdEngine sonarBridgeEngine; CpdSensor sensor; Settings settings; @Before public void setUp() { IndexFactory indexFactory = mock(IndexFactory.class); - sonarEngine = new SonarEngine(indexFactory, null, null); - sonarBridgeEngine = new SonarBridgeEngine(indexFactory, null, null); + sonarEngine = new JavaCpdEngine(indexFactory, null, null); + sonarBridgeEngine = new DefaultCpdEngine(indexFactory, new CpdMappings(), null, null, mock(BlockCache.class)); settings = new Settings(new PropertyDefinitions(CpdPlugin.class)); DefaultFileSystem fs = new DefaultFileSystem(); diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java index 3923b53fe21..924aa9193c8 100644 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; +import org.sonar.batch.duplication.BlockCache; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.anyString; @@ -33,15 +34,15 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class SonarBridgeEngineTest { +public class DefaultCpdEngineTest { - private SonarBridgeEngine engine; + private DefaultCpdEngine engine; private Settings settings; @Before public void init() { settings = new Settings(); - engine = new SonarBridgeEngine(null, null, null, settings); + engine = new DefaultCpdEngine(null, null, null, settings, mock(BlockCache.class)); } @Test @@ -61,53 +62,46 @@ public class SonarBridgeEngineTest { @Test public void shouldReturnDefaultBlockSize() { - assertThat(SonarBridgeEngine.getDefaultBlockSize("cobol")).isEqualTo(30); - assertThat(SonarBridgeEngine.getDefaultBlockSize("natur")).isEqualTo(20); - assertThat(SonarBridgeEngine.getDefaultBlockSize("abap")).isEqualTo(20); - assertThat(SonarBridgeEngine.getDefaultBlockSize("other")).isEqualTo(10); + assertThat(DefaultCpdEngine.getDefaultBlockSize("cobol")).isEqualTo(30); + assertThat(DefaultCpdEngine.getDefaultBlockSize("natur")).isEqualTo(20); + assertThat(DefaultCpdEngine.getDefaultBlockSize("abap")).isEqualTo(20); + assertThat(DefaultCpdEngine.getDefaultBlockSize("other")).isEqualTo(10); } @Test public void defaultBlockSize() { - Project project = newProject("foo"); - assertThat(engine.getBlockSize(project, "java")).isEqualTo(10); + assertThat(engine.getBlockSize("java")).isEqualTo(10); } @Test public void blockSizeForCobol() { - Project project = newProject("foo"); settings.setProperty("sonar.cpd.cobol.minimumLines", "42"); - assertThat(engine.getBlockSize(project, "cobol")).isEqualTo(42); + assertThat(engine.getBlockSize("cobol")).isEqualTo(42); } @Test public void defaultMinimumTokens() { - Project project = newProject("foo"); - - assertThat(engine.getMinimumTokens(project, "java")).isEqualTo(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE); + assertThat(engine.getMinimumTokens("java")).isEqualTo(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE); } @Test public void generalMinimumTokens() { - Project project = newProject("foo"); settings.setProperty("sonar.cpd.minimumTokens", 33); - assertThat(engine.getMinimumTokens(project, "java")).isEqualTo(33); + assertThat(engine.getMinimumTokens("java")).isEqualTo(33); } @Test public void minimumTokensByLanguage() { - Project javaProject = newProject("foo"); settings.setProperty("sonar.cpd.java.minimumTokens", "42"); settings.setProperty("sonar.cpd.php.minimumTokens", "33"); - assertThat(engine.getMinimumTokens(javaProject, "java")).isEqualTo(42); + assertThat(engine.getMinimumTokens("java")).isEqualTo(42); - Project phpProject = newProject("foo"); settings.setProperty("sonar.cpd.java.minimumTokens", "42"); settings.setProperty("sonar.cpd.php.minimumTokens", "33"); - assertThat(engine.getMinimumTokens(phpProject, "php")).isEqualTo(33); + assertThat(engine.getMinimumTokens("php")).isEqualTo(33); } private static Project newProject(String key) { diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java new file mode 100644 index 00000000000..770688d24a9 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java @@ -0,0 +1,147 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.cpd; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.duplication.DefaultDuplicationBuilder; +import org.sonar.batch.duplication.DuplicationCache; +import org.sonar.duplications.index.CloneGroup; +import org.sonar.duplications.index.ClonePart; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class JavaCpdEngineTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + SensorContext context = mock(SensorContext.class); + DeprecatedDefaultInputFile inputFile; + private DefaultDuplicationBuilder duplicationBuilder; + + @Before + public void before() throws IOException { + when(context.measureBuilder()).thenReturn(new DefaultMeasureBuilder()); + inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java"); + DuplicationCache duplicationCache = mock(DuplicationCache.class); + duplicationBuilder = spy(new DefaultDuplicationBuilder(inputFile, duplicationCache)); + when(context.duplicationBuilder(any(InputFile.class))).thenReturn(duplicationBuilder); + inputFile.setFile(temp.newFile("Foo.java")); + } + + @SuppressWarnings("unchecked") + @Test + public void testNothingToSave() { + JavaCpdEngine.save(context, inputFile, null); + JavaCpdEngine.save(context, inputFile, Collections.EMPTY_LIST); + + verifyZeroInteractions(context); + } + + @Test + public void testOneSimpleDuplicationBetweenTwoFiles() { + List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214))); + JavaCpdEngine.save(context, inputFile, groups); + + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(1).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(200).build()); + + InOrder inOrder = Mockito.inOrder(duplicationBuilder); + inOrder.verify(duplicationBuilder).originBlock(5, 204); + inOrder.verify(duplicationBuilder).isDuplicatedBy("key2", 15, 214); + inOrder.verify(duplicationBuilder).done(); + } + + @Test + public void testDuplicationOnSameFile() throws Exception { + List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key1", 0, 215, 414))); + JavaCpdEngine.save(context, inputFile, groups); + + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(2).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(400).build()); + + InOrder inOrder = Mockito.inOrder(duplicationBuilder); + inOrder.verify(duplicationBuilder).originBlock(5, 204); + inOrder.verify(duplicationBuilder).isDuplicatedBy("key1", 215, 414); + inOrder.verify(duplicationBuilder).done(); + } + + @Test + public void testOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception { + List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214), new ClonePart("key3", 0, 25, 224))); + JavaCpdEngine.save(context, inputFile, groups); + + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(1).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(200).build()); + + InOrder inOrder = Mockito.inOrder(duplicationBuilder); + inOrder.verify(duplicationBuilder).originBlock(5, 204); + inOrder.verify(duplicationBuilder).isDuplicatedBy("key2", 15, 214); + inOrder.verify(duplicationBuilder).isDuplicatedBy("key3", 25, 224); + inOrder.verify(duplicationBuilder).done(); + } + + @Test + public void testTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception { + List<CloneGroup> groups = Arrays.asList( + newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)), + newCloneGroup(new ClonePart("key1", 0, 15, 214), new ClonePart("key3", 0, 15, 214))); + JavaCpdEngine.save(context, inputFile, groups); + + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(2).build()); + verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(210).build()); + + InOrder inOrder = Mockito.inOrder(duplicationBuilder); + inOrder.verify(duplicationBuilder).originBlock(5, 204); + inOrder.verify(duplicationBuilder).isDuplicatedBy("key2", 15, 214); + inOrder.verify(duplicationBuilder).originBlock(15, 214); + inOrder.verify(duplicationBuilder).isDuplicatedBy("key3", 15, 214); + inOrder.verify(duplicationBuilder).done(); + } + + private CloneGroup newCloneGroup(ClonePart... parts) { + return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build(); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java deleted file mode 100644 index c8551a74166..00000000000 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.cpd; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.batch.SensorContext; -import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.test.IsMeasure; -import org.sonar.duplications.index.CloneGroup; -import org.sonar.duplications.index.ClonePart; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.*; - -public class SonarEngineTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - SensorContext context = mock(SensorContext.class); - DeprecatedDefaultInputFile inputFile; - - @Before - public void before() throws IOException { - inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java"); - inputFile.setFile(temp.newFile("Foo.java")); - } - - @SuppressWarnings("unchecked") - @Test - public void testNothingToSave() { - SonarEngine.save(context, inputFile, null); - SonarEngine.save(context, inputFile, Collections.EMPTY_LIST); - - verifyZeroInteractions(context); - } - - @Test - public void testOneSimpleDuplicationBetweenTwoFiles() { - List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214))); - SonarEngine.save(context, inputFile, groups); - - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 1d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 200d); - verify(context).saveMeasure( - eq(inputFile), - argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>" - + "<b s=\"5\" l=\"200\" r=\"key1\"/>" - + "<b s=\"15\" l=\"200\" r=\"key2\"/>" - + "</g></duplications>"))); - } - - @Test - public void testDuplicationOnSameFile() throws Exception { - List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key1", 0, 215, 414))); - SonarEngine.save(context, inputFile, groups); - - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 400d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 2d); - verify(context).saveMeasure( - eq(inputFile), - argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>" - + "<b s=\"5\" l=\"200\" r=\"key1\"/>" - + "<b s=\"215\" l=\"200\" r=\"key1\"/>" - + "</g></duplications>"))); - } - - @Test - public void testOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception { - List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214), new ClonePart("key3", 0, 25, 224))); - SonarEngine.save(context, inputFile, groups); - - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 1d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 200d); - verify(context).saveMeasure( - eq(inputFile), - argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>" - + "<b s=\"5\" l=\"200\" r=\"key1\"/>" - + "<b s=\"15\" l=\"200\" r=\"key2\"/>" - + "<b s=\"25\" l=\"200\" r=\"key3\"/>" - + "</g></duplications>"))); - } - - @Test - public void testTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception { - List<CloneGroup> groups = Arrays.asList( - newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)), - newCloneGroup(new ClonePart("key1", 0, 15, 214), new ClonePart("key3", 0, 15, 214))); - SonarEngine.save(context, inputFile, groups); - - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 2d); - verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 210d); - verify(context).saveMeasure( - eq(inputFile), - argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" - + "<g>" - + "<b s=\"5\" l=\"200\" r=\"key1\"/>" - + "<b s=\"15\" l=\"200\" r=\"key2\"/>" - + "</g>" - + "<g>" - + "<b s=\"15\" l=\"200\" r=\"key1\"/>" - + "<b s=\"15\" l=\"200\" r=\"key3\"/>" - + "</g>" - + "</duplications>"))); - } - - @Test - public void shouldEscapeXmlEntities() throws IOException { - InputFile csharpFile = new DeprecatedDefaultInputFile("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs") - .setFile(temp.newFile("SubsRedsDelivery.cs")); - List<CloneGroup> groups = Arrays.asList(newCloneGroup( - new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs", 0, 5, 204), - new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery2.cs", 0, 15, 214))); - SonarEngine.save(context, csharpFile, groups); - - verify(context).saveMeasure( - eq(csharpFile), - argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>" - + "<b s=\"5\" l=\"200\" r=\"Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs\"/>" - + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs & Reds/SubsRedsDelivery2.cs\"/>" - + "</g></duplications>"))); - } - - private CloneGroup newCloneGroup(ClonePart... parts) { - return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build(); - } - -} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java index 45510dc40ac..7b9fa524fec 100644 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java @@ -25,12 +25,14 @@ import org.slf4j.Logger; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; +import org.sonar.batch.bootstrap.AnalysisMode; import org.sonar.batch.index.ResourcePersister; import org.sonar.core.duplication.DuplicationDao; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class IndexFactoryTest { @@ -38,41 +40,43 @@ public class IndexFactoryTest { Settings settings; IndexFactory factory; Logger logger; + private AnalysisMode analysisMode; @Before public void setUp() { project = new Project("foo"); settings = new Settings(); - factory = new IndexFactory(settings, mock(ResourcePersister.class), mock(DuplicationDao.class)); + analysisMode = mock(AnalysisMode.class); + factory = new IndexFactory(analysisMode, settings, mock(ResourcePersister.class), mock(DuplicationDao.class)); logger = mock(Logger.class); } @Test public void crossProjectEnabled() { - settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true"); + settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true"); assertThat(factory.verifyCrossProject(project, logger)).isTrue(); verify(logger).info("Cross-project analysis enabled"); } @Test public void noCrossProjectWithBranch() { - settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true"); - project.setBranch("branch"); + settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true"); + settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "branch"); assertThat(factory.verifyCrossProject(project, logger)).isFalse(); verify(logger).info("Cross-project analysis disabled. Not supported on project branches."); } @Test - public void cross_project_should_be_disabled_on_dry_run() { - settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true"); - settings.setProperty(CoreProperties.DRY_RUN, "true"); + public void cross_project_should_be_disabled_on_preview() { + when(analysisMode.isPreview()).thenReturn(true); + settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true"); assertThat(factory.verifyCrossProject(project, logger)).isFalse(); - verify(logger).info("Cross-project analysis disabled. Not supported on dry runs."); + verify(logger).info("Cross-project analysis disabled. Not supported in preview mode."); } @Test public void crossProjectDisabled() { - settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "false"); + settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "false"); assertThat(factory.verifyCrossProject(project, logger)).isFalse(); verify(logger).info("Cross-project analysis disabled"); } diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java new file mode 100644 index 00000000000..21876b8f3d5 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.plugins.cpd.medium; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.duplication.DuplicationGroup; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; +import org.sonar.plugins.cpd.CpdPlugin; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; + +public class CpdMediumTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .registerPlugin("cpd", new CpdPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) + .build(); + + private File baseDir; + + private ImmutableMap.Builder<String, String> builder; + + @Before + public void prepare() throws IOException { + tester.start(); + + baseDir = temp.newFolder(); + + builder = ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project"); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testDuplications() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String duplicatedStuff = "Sample xoo\ncontent\nfoo\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti"; + + File xooFile1 = new File(srcDir, "sample1.xoo"); + FileUtils.write(xooFile1, duplicatedStuff); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, duplicatedStuff); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.minimumTokens", "10") + .put("sonar.verbose", "true") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(2); + + // 4 measures per file + assertThat(result.measures()).hasSize(6); + + InputFile inputFile = result.inputFiles().get(0); + // One clone group + List<DuplicationGroup> duplicationGroups = result.duplicationsFor(inputFile); + assertThat(duplicationGroups).hasSize(1); + + DuplicationGroup cloneGroup = duplicationGroups.get(0); + assertThat(cloneGroup.duplicates()).hasSize(1); + assertThat(cloneGroup.originBlock().startLine()).isEqualTo(1); + assertThat(cloneGroup.originBlock().length()).isEqualTo(17); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/XooConstants.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java index c59091fb0f6..62f7d2ba782 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/XooConstants.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.base; +package org.sonar.xoo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java index 95b65dd31a3..3477fab825c 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java @@ -20,6 +20,14 @@ package org.sonar.xoo; import org.sonar.api.SonarPlugin; +import org.sonar.xoo.lang.MeasureSensor; +import org.sonar.xoo.lang.ScmActivitySensor; +import org.sonar.xoo.lang.SymbolReferencesSensor; +import org.sonar.xoo.lang.SyntaxHighlightingSensor; +import org.sonar.xoo.lang.XooTokenizerSensor; +import org.sonar.xoo.rule.CreateIssueByInternalKeySensor; +import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor; +import org.sonar.xoo.rule.OneIssuePerLineSensor; import org.sonar.xoo.rule.XooQualityProfile; import org.sonar.xoo.rule.XooRulesDefinition; @@ -39,7 +47,19 @@ public class XooPlugin extends SonarPlugin { return Arrays.asList( Xoo.class, XooRulesDefinition.class, - XooQualityProfile.class); + XooQualityProfile.class, + + // sensors + MeasureSensor.class, + ScmActivitySensor.class, + SyntaxHighlightingSensor.class, + SymbolReferencesSensor.class, + XooTokenizerSensor.class, + + OneIssuePerLineSensor.class, + OneIssueOnDirPerFileSensor.class, + CreateIssueByInternalKeySensor.class + ); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java index c28ebd5a05c..56e013b3716 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.lang; +package org.sonar.xoo.lang; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; @@ -29,8 +29,8 @@ import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.MeasureBuilder; import org.sonar.api.measures.CoreMetrics; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooConstants; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivitySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java index b3352dba62a..12663b0b697 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivitySensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java @@ -17,11 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.lang; - -import org.sonar.api.batch.sensor.Sensor; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.batch.sensor.SensorDescriptor; +package org.sonar.xoo.lang; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; @@ -31,11 +27,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.FileLinesContext; import org.sonar.api.measures.FileLinesContextFactory; import org.sonar.api.utils.DateUtils; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; +import org.sonar.xoo.Xoo; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java index 91fa61e5c78..c8cafc2d705 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.lang; +package org.sonar.xoo.lang; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; @@ -29,8 +29,8 @@ import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.symbol.Symbol; import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; import org.sonar.api.measures.CoreMetrics; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooConstants; import java.io.File; import java.io.IOException; @@ -83,7 +83,7 @@ public class SymbolReferencesSensor implements Sensor { @Override public void describe(SensorDescriptor descriptor) { descriptor - .name("Xoo Highlighting Sensor") + .name("Xoo Symbol Reference Sensor") .provides(CoreMetrics.LINES) .workOnLanguages(Xoo.KEY) .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java index 5b78759dbb9..0ae23954442 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.lang; +package org.sonar.xoo.lang; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; @@ -28,8 +28,8 @@ import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.measures.CoreMetrics; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooConstants; import java.io.File; import java.io.IOException; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java new file mode 100644 index 00000000000..1ee33e560ef --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java @@ -0,0 +1,84 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.xoo.lang; + +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.apache.commons.io.FileUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.duplication.TokenBuilder; +import org.sonar.xoo.Xoo; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * Parse files *.xoo.highlighting + */ +public class XooTokenizerSensor implements Sensor { + + private void computeTokens(InputFile inputFile, SensorContext context) { + TokenBuilder tokenBuilder = context.tokenBuilder(inputFile); + File ioFile = inputFile.file(); + int lineId = 0; + try { + for (String line : FileUtils.readLines(ioFile)) { + lineId++; + for (String token : Splitter.on(" ").split(line)) { + tokenBuilder.addToken(lineId, token); + } + } + tokenBuilder.done(); + } catch (IOException e) { + throw new IllegalStateException("Unable to read file " + ioFile, e); + } + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("Xoo Tokenizer Sensor") + .workOnLanguages(Xoo.KEY) + .workOnFileTypes(InputFile.Type.MAIN); + } + + @Override + public void execute(SensorContext context) { + String[] cpdExclusions = context.settings().getStringArray(CoreProperties.CPD_EXCLUSIONS); + FilePredicates p = context.fileSystem().predicates(); + List<InputFile> sourceFiles = Lists.newArrayList(context.fileSystem().inputFiles(p.and( + p.hasType(InputFile.Type.MAIN), + p.hasLanguage(Xoo.KEY), + p.doesNotMatchPathPatterns(cpdExclusions) + ))); + if (sourceFiles.isEmpty()) { + return; + } + for (InputFile file : sourceFiles) { + computeTokens(file, context); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/CreateIssueByInternalKeySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java index 1ee150a5c01..c3e7d2641e6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/CreateIssueByInternalKeySensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java @@ -17,15 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.rule; +package org.sonar.xoo.rule; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.rule.ActiveRule; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooConstants; public class CreateIssueByInternalKeySensor implements Sensor { @@ -36,6 +36,7 @@ public class CreateIssueByInternalKeySensor implements Sensor { descriptor .name("CreateIssueByInternalKeySensor") .workOnLanguages(Xoo.KEY) + .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY) .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java index 2a02ebef9dd..c2478830975 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.rule; +package org.sonar.xoo.rule; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; @@ -25,8 +25,8 @@ import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.rule.RuleKey; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooConstants; public class OneIssueOnDirPerFileSensor implements Sensor { @@ -37,6 +37,7 @@ public class OneIssueOnDirPerFileSensor implements Sensor { descriptor .name("One Issue On Dir Per File") .workOnLanguages(Xoo.KEY) + .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY) .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java index 2a811068e54..bc0697b64b4 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.rule; +package org.sonar.xoo.rule; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; @@ -28,8 +28,8 @@ import org.sonar.api.batch.sensor.issue.IssueBuilder; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooConstants; public class OneIssuePerLineSensor implements Sensor { @@ -43,6 +43,7 @@ public class OneIssuePerLineSensor implements Sensor { .name("One Issue Per Line") .dependsOn(CoreMetrics.LINES) .workOnLanguages(Xoo.KEY) + .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY) .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); } diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java index b65ad573daa..bfe1fd99afa 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java @@ -27,6 +27,6 @@ public class XooPluginTest { @Test public void provide_extensions() { - assertThat(new XooPlugin().getExtensions()).hasSize(3); + assertThat(new XooPlugin().getExtensions()).hasSize(11); } } @@ -576,6 +576,12 @@ <version>${project.version}</version> </dependency> <dependency> + <groupId>org.codehaus.sonar.plugins</groupId> + <artifactId>sonar-xoo-plugin</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar-squid</artifactId> <version>4.1</version> diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java index c5b3c5632f8..be4f94f5381 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java @@ -20,9 +20,11 @@ package org.sonar.server.batch; +import com.google.common.collect.Maps; import org.apache.commons.io.IOUtils; import org.sonar.api.resources.Language; import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.RequestHandler; import org.sonar.api.server.ws.Response; @@ -47,9 +49,11 @@ import org.sonar.server.user.UserSession; import javax.annotation.Nullable; +import java.util.Collections; import java.util.List; import java.util.Map; +import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; public class ProjectReferentialsAction implements RequestHandler { @@ -84,7 +88,7 @@ public class ProjectReferentialsAction implements RequestHandler { action .createParam(PARAM_KEY) .setRequired(true) - .setDescription("Project key") + .setDescription("Project or module key") .setExampleValue("org.codehaus.sonar:sonar"); action @@ -100,10 +104,21 @@ public class ProjectReferentialsAction implements RequestHandler { DbSession session = dbClient.openSession(false); try { - ProjectReferentials ref = new ProjectReferentials(); - String projectKey = request.mandatoryParam(PARAM_KEY); + String projectOrModuleKey = request.mandatoryParam(PARAM_KEY); String profileName = request.param(PARAM_PROFILE); - addSettings(ref, projectKey, hasScanPerm, session); + ProjectReferentials ref = new ProjectReferentials(); + + String projectKey = null; + ComponentDto module = dbClient.componentDao().getNullableByKey(session, projectOrModuleKey); + if (module != null) { + ComponentDto project = !module.qualifier().equals(Qualifiers.PROJECT) ? dbClient.componentDao().getRootProjectByKey(projectOrModuleKey, session) : module; + if (!project.key().equals(module.key())) { + addSettings(ref, module.getKey(), getSettingsFromParentModules(module.key(), hasScanPerm, session)); + } + projectKey = project.key(); + addSettingsToChildrenModules(ref, projectOrModuleKey, Maps.<String, String>newHashMap(), hasScanPerm, session); + } + addProfiles(ref, projectKey, profileName, session); addActiveRules(ref); @@ -114,14 +129,43 @@ public class ProjectReferentialsAction implements RequestHandler { } } - private void addSettings(ProjectReferentials ref, String projectKey, boolean hasScanPerm, DbSession session) { - addSettings(ref, projectKey, propertiesDao.selectProjectProperties(projectKey, session), hasScanPerm); + private Map<String, String> getSettingsFromParentModules(String moduleKey, boolean hasScanPerm, DbSession session) { + List<ComponentDto> parents = newArrayList(); + aggregateParentModules(moduleKey, parents, session); + Collections.reverse(parents); + + Map<String, String> parentProperties = newHashMap(); + for (ComponentDto parent : parents) { + parentProperties.putAll(getPropertiesMap(propertiesDao.selectProjectProperties(parent.key(), session), hasScanPerm)); + } + return parentProperties; + } + + private void aggregateParentModules(String component, List<ComponentDto> parents, DbSession session){ + ComponentDto parent = dbClient.componentDao().getParentModuleByKey(component, session); + if (parent != null) { + parents.add(parent); + aggregateParentModules(parent.key(), parents, session); + } + } + + private void addSettingsToChildrenModules(ProjectReferentials ref, String projectKey, Map<String, String> parentProperties, boolean hasScanPerm, DbSession session) { + parentProperties.putAll(getPropertiesMap(propertiesDao.selectProjectProperties(projectKey, session), hasScanPerm)); + addSettings(ref, projectKey, parentProperties); + for (ComponentDto module : dbClient.componentDao().findModulesByProject(projectKey, session)) { - addSettings(ref, module.getKey(), propertiesDao.selectProjectProperties(module.getKey(), session), hasScanPerm); + addSettings(ref, module.key(), parentProperties); + addSettingsToChildrenModules(ref, module.key(), parentProperties, hasScanPerm, session); + } + } + + private void addSettings(ProjectReferentials ref, String module, Map<String, String> properties) { + if (!properties.isEmpty()) { + ref.addSettings(module, properties); } } - private void addSettings(ProjectReferentials ref, String projectOrModuleKey, List<PropertyDto> propertyDtos, boolean hasScanPerm) { + private Map<String, String> getPropertiesMap(List<PropertyDto> propertyDtos, boolean hasScanPerm) { Map<String, String> properties = newHashMap(); for (PropertyDto propertyDto : propertyDtos) { String key = propertyDto.getKey(); @@ -130,16 +174,14 @@ public class ProjectReferentialsAction implements RequestHandler { properties.put(key, value); } } - if (!properties.isEmpty()) { - ref.addSettings(projectOrModuleKey, properties); - } + return properties; } private static boolean isPropertyAllowed(String key, boolean hasScanPerm) { return !key.contains(".secured") || hasScanPerm; } - private void addProfiles(ProjectReferentials ref, String projectKey, @Nullable String profileName, DbSession session) { + private void addProfiles(ProjectReferentials ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) { for (Language language : languages.all()) { String languageKey = language.getKey(); QualityProfileDto qualityProfileDto = getProfile(languageKey, projectKey, profileName, session); @@ -153,12 +195,16 @@ public class ProjectReferentialsAction implements RequestHandler { /** * First try to find a quality profile matching the given name (if provided) and current language - * If null, try to find the quality profile set on the project - * If null, try to find the default profile of the language + * If no profile found, try to find the quality profile set on the project (if provided) + * If still no profile found, try to find the default profile of the language + * + * Never return null because a default profile should always be set on ech language */ - private QualityProfileDto getProfile(String languageKey, String projectKey, @Nullable String profileName, DbSession session) { + private QualityProfileDto getProfile(String languageKey, @Nullable String projectKey, @Nullable String profileName, DbSession session) { QualityProfileDto qualityProfileDto = profileName != null ? qProfileFactory.getByNameAndLanguage(session, profileName, languageKey) : null; - qualityProfileDto = qualityProfileDto != null ? qualityProfileDto : qProfileFactory.getByProjectAndLanguage(session, projectKey, languageKey); + if (qualityProfileDto == null && projectKey != null) { + qualityProfileDto = qProfileFactory.getByProjectAndLanguage(session, projectKey, languageKey); + } qualityProfileDto = qualityProfileDto != null ? qualityProfileDto : qProfileFactory.getDefault(session, languageKey); if (qualityProfileDto != null) { return qualityProfileDto; @@ -177,8 +223,7 @@ public class ProjectReferentialsAction implements RequestHandler { rule.name(), activeRule.severity(), rule.internalKey(), - qProfile.language() - ); + qProfile.language()); for (Map.Entry<String, String> entry : activeRule.params().entrySet()) { inputActiveRule.addParam(entry.getKey(), entry.getValue()); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/persistence/ComponentDao.java b/server/sonar-server/src/main/java/org/sonar/server/component/persistence/ComponentDao.java index 39a23370fdf..657287999b7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/persistence/ComponentDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/persistence/ComponentDao.java @@ -26,6 +26,9 @@ import org.sonar.core.component.db.ComponentMapper; import org.sonar.core.persistence.DaoComponent; import org.sonar.core.persistence.DbSession; import org.sonar.server.db.BaseDao; +import org.sonar.server.exceptions.NotFoundException; + +import javax.annotation.CheckForNull; import java.util.Date; import java.util.List; @@ -47,6 +50,19 @@ public class ComponentDao extends BaseDao<ComponentMapper, ComponentDto, String> return mapper(session).countById(id) > 0; } + public ComponentDto getRootProjectByKey(String componentKey, DbSession session) { + ComponentDto componentDto = mapper(session).selectRootProjectByKey(componentKey); + if (componentDto == null) { + throw new NotFoundException(String.format("Root project for project '%s' not found", componentKey)); + } + return componentDto; + } + + @CheckForNull + public ComponentDto getParentModuleByKey(String componentKey, DbSession session) { + return mapper(session).selectParentModuleByKey(componentKey); + } + public List<ComponentDto> findModulesByProject(String projectKey, DbSession session) { return mapper(session).findModulesByProject(projectKey); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/DatabaseChecker.java b/server/sonar-server/src/main/java/org/sonar/server/db/DatabaseChecker.java index c37b192f919..1b3fc5aeedd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/DatabaseChecker.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/DatabaseChecker.java @@ -19,8 +19,10 @@ */ package org.sonar.server.db; +import com.google.common.base.Throwables; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.lang.StringUtils; +import org.picocontainer.Startable; import org.slf4j.LoggerFactory; import org.sonar.api.ServerComponent; import org.sonar.core.persistence.Database; @@ -30,7 +32,7 @@ import org.sonar.core.persistence.dialect.Oracle; import java.sql.Connection; import java.sql.SQLException; -public class DatabaseChecker implements ServerComponent { +public class DatabaseChecker implements ServerComponent, Startable { private final Database db; @@ -38,14 +40,24 @@ public class DatabaseChecker implements ServerComponent { this.db = db; } - public void start() throws SQLException { - if (H2.ID.equals(db.getDialect().getId())) { - LoggerFactory.getLogger(DatabaseChecker.class).warn("H2 database should be used for evaluation purpose only"); - } else if (Oracle.ID.equals(db.getDialect().getId())) { - checkOracleDriverVersion(); + @Override + public void start() { + try { + if (H2.ID.equals(db.getDialect().getId())) { + LoggerFactory.getLogger(DatabaseChecker.class).warn("H2 database should be used for evaluation purpose only"); + } else if (Oracle.ID.equals(db.getDialect().getId())) { + checkOracleDriverVersion(); + } + } catch (Exception e) { + Throwables.propagate(e); } } + @Override + public void stop() { + // nothing to do + } + private void checkOracleDriverVersion() throws SQLException { Connection connection = db.getDataSource().getConnection(); try { diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java index 8d36fbe9b83..32ebbaf443e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/BaseDataChange.java @@ -39,6 +39,10 @@ public abstract class BaseDataChange implements DataChange, DatabaseMigration { try { readConnection = db.getDataSource().getConnection(); readConnection.setAutoCommit(false); + if (readConnection.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)) { + readConnection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + } + writeConnection = db.getDataSource().getConnection(); writeConnection.setAutoCommit(false); Context context = new Context(db, readConnection, writeConnection); diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java index da1d3f4b8bf..cefaef18d30 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/UpsertImpl.java @@ -36,6 +36,7 @@ public class UpsertImpl extends BaseSqlStatement<Upsert> implements Upsert { @Override public Upsert addBatch() throws SQLException { pstmt.addBatch(); + pstmt.clearParameters(); batchCount++; if (batchCount % BatchSession.MAX_BATCH_SIZE == 0L) { pstmt.executeBatch(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java index 0e9f286f265..ccc2afb7032 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java @@ -27,6 +27,8 @@ import org.sonar.core.component.ComponentDto; import org.sonar.core.persistence.DbSession; import org.sonar.server.component.persistence.ComponentDao; +import javax.annotation.Nullable; + import java.util.List; import java.util.Map; @@ -90,42 +92,45 @@ public class DuplicationsJsonWriter implements ServerComponent { ComponentDto file = componentDao.getNullableByKey(session, componentKey); if (file != null) { json.name(ref).beginObject(); - json.prop("key", file.key()); - json.prop("name", file.longName()); - - Long projectId = file.projectId(); - ComponentDto project = projectsById.get(file.projectId()); - if (project == null && projectId != null) { - project = componentDao.getById(projectId, session); - if (project != null) { - projectsById.put(projectId, project); - } - } - - Long subProjectId = file.subProjectId(); - ComponentDto subProject = subProjectsById.get(subProjectId); - if (subProject == null && subProjectId != null) { - subProject = componentDao.getById(subProjectId, session); - if (subProject != null) { - subProjectsById.put(subProject.getId(), subProject); - } - } - - if (project != null) { - json.prop("project", project.key()); - json.prop("projectName", project.longName()); - - // Do not return sub project if sub project and project are the same - boolean displaySubProject = subProject != null && !subProject.getId().equals(project.getId()); - if (displaySubProject) { - json.prop("subProject", subProject.key()); - json.prop("subProjectName", subProject.longName()); - } - } + + addFile(json, file); + ComponentDto project = getProject(file.projectId(), projectsById, session); + ComponentDto subProject = getProject(file.subProjectId(), subProjectsById, session); + addProject(json, project, subProject); json.endObject(); } } } + private void addFile(JsonWriter json, ComponentDto file) { + json.prop("key", file.key()); + json.prop("name", file.longName()); + } + + private void addProject(JsonWriter json, @Nullable ComponentDto project, @Nullable ComponentDto subProject) { + if (project != null) { + json.prop("project", project.key()); + json.prop("projectName", project.longName()); + + // Do not return sub project if sub project and project are the same + boolean displaySubProject = subProject != null && !subProject.getId().equals(project.getId()); + if (displaySubProject) { + json.prop("subProject", subProject.key()); + json.prop("subProjectName", subProject.longName()); + } + } + } + + private ComponentDto getProject(@Nullable Long projectId, Map<Long, ComponentDto> projectsById, DbSession session) { + ComponentDto project = projectsById.get(projectId); + if (project == null && projectId != null) { + project = componentDao.getById(projectId, session); + if (project != null) { + projectsById.put(project.getId(), project); + } + } + return project; + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/RulesAggregation.java b/server/sonar-server/src/main/java/org/sonar/server/issue/RulesAggregation.java index f6f0fb7fade..920b84c60dc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/RulesAggregation.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/RulesAggregation.java @@ -36,7 +36,7 @@ public class RulesAggregation { } public RulesAggregation add(RuleDto ruleDto) { - rules.add(new Rule().setRuleKey(ruleDto.getKey()).setName(ruleDto.getName())); + rules.add(new Rule(ruleDto.getKey(), ruleDto.getName())); return this; } @@ -53,24 +53,19 @@ public class RulesAggregation { private RuleKey ruleKey; private String name; - public RuleKey ruleKey() { - return ruleKey; + public Rule(RuleKey ruleKey, String name) { + this.ruleKey = ruleKey; + this.name = name; } - public Rule setRuleKey(RuleKey ruleKey) { - this.ruleKey = ruleKey; - return this; + public RuleKey ruleKey() { + return ruleKey; } public String name() { return name; } - public Rule setName(String name) { - this.name = name; - return this; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/MeasureFilterFactory.java b/server/sonar-server/src/main/java/org/sonar/server/measure/MeasureFilterFactory.java index 06d5143a6d0..bb6dcdda810 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/MeasureFilterFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/MeasureFilterFactory.java @@ -22,6 +22,7 @@ package org.sonar.server.measure; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.sonar.api.ServerComponent; @@ -33,11 +34,10 @@ import org.sonar.api.utils.System2; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Map; + +import java.util.*; + +import static com.google.common.collect.Lists.newArrayList; public class MeasureFilterFactory implements ServerComponent { @@ -62,7 +62,7 @@ public class MeasureFilterFactory implements ServerComponent { if (onBaseComponents != null) { filter.setOnBaseResourceChildren(Boolean.valueOf(onBaseComponents)); } - filter.setResourceName((String) properties.get("nameSearch")); + filter.setResourceName(toString(properties.get("nameSearch"))); filter.setResourceKey((String) properties.get("keySearch")); String onFavourites = (String) properties.get("onFavourites"); if (onFavourites != null) { @@ -111,7 +111,10 @@ public class MeasureFilterFactory implements ServerComponent { } } } else { - filter.setSortOn(MeasureFilterSort.Field.valueOf(s.toUpperCase())); + String sort = s.toUpperCase(); + if (sortFieldLabels().contains(sort)) { + filter.setSortOn(MeasureFilterSort.Field.valueOf(sort)); + } } } @@ -120,6 +123,15 @@ public class MeasureFilterFactory implements ServerComponent { } } + private List<String> sortFieldLabels(){ + return newArrayList(Iterables.transform(Arrays.asList(MeasureFilterSort.Field.values()), new Function<MeasureFilterSort.Field, String>() { + @Override + public String apply(@Nullable MeasureFilterSort.Field input) { + return input != null ? input.name() : null; + } + })); + } + @CheckForNull private MeasureFilterCondition toCondition(Map<String, Object> props, int index) { MeasureFilterCondition condition = null; @@ -139,7 +151,7 @@ public class MeasureFilterFactory implements ServerComponent { } @CheckForNull - private MeasureFilterCondition alertToCondition(List<String> alertLevels) { + private MeasureFilterCondition alertToCondition(@Nullable List<String> alertLevels) { if (alertLevels == null || alertLevels.isEmpty()) { return null; } @@ -148,7 +160,7 @@ public class MeasureFilterFactory implements ServerComponent { String op = "in"; List<String> alertLevelsUppercase = Lists.transform(alertLevels, new Function<String, String>() { @Override - public String apply(String input) { + public String apply(@Nullable String input) { return input != null ? input.toUpperCase() : ""; } }); @@ -192,4 +204,19 @@ public class MeasureFilterFactory implements ServerComponent { return null; } + @CheckForNull + public static String toString(@Nullable Object o) { + if (o != null) { + if (o instanceof List) { + return Joiner.on(",").join((List) o); + } else if (o instanceof String[]) { + // assume that it contains only strings + return Joiner.on(",").join((String[]) o); + } else { + return o.toString(); + } + } + return null; + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java index b5fe7007e9b..7979bca8a8c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java @@ -22,7 +22,6 @@ package org.sonar.server.platform; import com.google.common.collect.Lists; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; @@ -41,8 +40,6 @@ public class ServerIdGenerator { private static final Pattern ORGANIZATION_PATTERN = Pattern.compile("[a-zA-Z0-9]+[a-zA-Z0-9 ]*"); - private static final Logger LOG = LoggerFactory.getLogger(ServerIdGenerator.class); - /** * Increment this version each time the algorithm is changed. Do not exceed 9. */ diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java index 4643ea4f641..65c0f46025f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java @@ -177,6 +177,7 @@ public class QProfileFactory implements ServerComponent { } } + @CheckForNull public QualityProfileDto getByProjectAndLanguage(DbSession session, String projectKey, String language) { return db.qualityProfileDao().getByProjectAndLanguage(projectKey, language, PROFILE_PROPERTY_PREFIX + language, session); } @@ -190,6 +191,7 @@ public class QProfileFactory implements ServerComponent { } } + @CheckForNull public QualityProfileDto getByNameAndLanguage(DbSession session, String name, String language) { return db.qualityProfileDao().getByNameAndLanguage(name, language, session); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileProjectOperations.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileProjectOperations.java index da24181f9fb..429b192a336 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileProjectOperations.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileProjectOperations.java @@ -53,12 +53,12 @@ public class QProfileProjectOperations implements ServerComponent { void addProject(int profileId, long projectId, UserSession userSession, DbSession session) { checkPermission(userSession); - ComponentDto project = (ComponentDto) findProjectNotNull(projectId, session); - QualityProfileDto qualityProfile = findNotNull(profileId, session); + ComponentDto project = (ComponentDto) findProjectNotNull(projectId, session); + QualityProfileDto qualityProfile = findNotNull(profileId, session); - db.propertiesDao().setProperty(new PropertyDto().setKey( - QProfileProjectLookup.PROFILE_PROPERTY_PREFIX + qualityProfile.getLanguage()).setValue(qualityProfile.getName()).setResourceId(project.getId()), session); - session.commit(); + db.propertiesDao().setProperty(new PropertyDto().setKey( + QProfileProjectLookup.PROFILE_PROPERTY_PREFIX + qualityProfile.getLanguage()).setValue(qualityProfile.getName()).setResourceId(project.getId()), session); + session.commit(); } public void removeProject(int profileId, long projectId, UserSession userSession) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java index 2885b4c1dd8..ca4e42f7a9d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -22,6 +22,7 @@ package org.sonar.server.rule.index; import com.google.common.base.Preconditions; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchScrollRequestBuilder; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; @@ -416,12 +417,27 @@ public class RuleIndex extends BaseIndex<Rule, RuleDto, RuleKey> { public List<Rule> getByIds(Collection<Integer> ids) { SearchRequestBuilder request = getClient().prepareSearch(this.getIndexName()) .setTypes(this.getIndexType()) + .setSearchType(SearchType.SCAN) + .setScroll(TimeValue.timeValueSeconds(3L)) + .setSize(100) .setQuery(QueryBuilders.termsQuery(RuleNormalizer.RuleField.ID.field(), ids)); - SearchResponse response = node.execute(request); + SearchResponse scrollResp = node.execute(request); List<Rule> rules = newArrayList(); - for (SearchHit hit : response.getHits()) { - rules.add(toDoc(hit.getSource())); + while (true) { + SearchScrollRequestBuilder scrollRequest = getClient() + .prepareSearchScroll(scrollResp.getScrollId()) + .setScroll(TimeValue.timeValueSeconds(3L)); + + scrollResp = node.execute(scrollRequest); + + for (SearchHit hit : scrollResp.getHits()) { + rules.add(toDoc(hit.getSource())); + } + //Break condition: No hits are returned + if (scrollResp.getHits().getHits().length == 0) { + break; + } } return rules; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java index 3b9533951bb..a57469683ba 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java @@ -36,6 +36,7 @@ import org.sonar.server.text.MacroInterpreter; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + import java.util.Collection; import java.util.Map; @@ -165,12 +166,18 @@ public class RuleMapping extends BaseMapping<RuleDoc, RuleMappingContext> { public void write(Rule rule, JsonWriter json, @Nullable SearchOptions options) { RuleMappingContext context = new RuleMappingContext(); - if (needDebtCharacteristicNames(options) && rule.debtCharacteristicKey() != null) { - // load debt characteristics if requested - context.add(debtModel.characteristicByKey(rule.debtCharacteristicKey())); + if (needDebtCharacteristicNames(options)) { + String debtCharacteristicKey = rule.debtCharacteristicKey(); + if (debtCharacteristicKey != null) { + // load debt characteristics if requested + context.add(debtModel.characteristicByKey(debtCharacteristicKey)); + } } - if (needDebtSubCharacteristicNames(options) && rule.debtSubCharacteristicKey() != null) { - context.add(debtModel.characteristicByKey(rule.debtSubCharacteristicKey())); + if (needDebtSubCharacteristicNames(options)) { + String debtSubCharacteristicKey = rule.debtSubCharacteristicKey(); + if (debtSubCharacteristicKey != null) { + context.add(debtModel.characteristicByKey(debtSubCharacteristicKey)); + } } doWrite((RuleDoc) rule, context, json, options); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java index 8f34fe3015c..2d90ff52a2b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java @@ -31,7 +31,6 @@ import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.qualityprofile.ActiveRule; import org.sonar.server.rule.Rule; import org.sonar.server.rule.RuleService; -import org.sonar.server.rule.index.RuleDoc; import org.sonar.server.rule.index.RuleNormalizer; import org.sonar.server.rule.index.RuleQuery; import org.sonar.server.search.FacetValue; @@ -259,7 +258,7 @@ public class SearchAction implements RequestHandler { private void writeRules(Result<Rule> result, JsonWriter json, SearchOptions options) { json.name("rules").beginArray(); for (Rule rule : result.getHits()) { - mapping.write((RuleDoc) rule, json, options); + mapping.write(rule, json, options); } json.endArray(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java index e6a635b149f..65e1995054f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java @@ -28,6 +28,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.resources.Language; import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Qualifiers; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.core.component.ComponentDto; @@ -47,6 +48,8 @@ import org.sonar.server.rule.RuleService; import org.sonar.server.user.MockUserSession; import org.sonar.server.ws.WsTester; +import java.util.Collections; + import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -81,70 +84,194 @@ public class ProjectReferentialsActionTest { WsTester tester; + ComponentDto project; + ComponentDto module; + ComponentDto subModule; + @Before public void setUp() throws Exception { DbClient dbClient = mock(DbClient.class); when(dbClient.openSession(false)).thenReturn(session); when(dbClient.componentDao()).thenReturn(componentDao); + project = new ComponentDto().setKey("org.codehaus.sonar:sonar").setQualifier(Qualifiers.PROJECT); + module = new ComponentDto().setKey("org.codehaus.sonar:sonar-server").setQualifier(Qualifiers.MODULE); + subModule = new ComponentDto().setKey("org.codehaus.sonar:sonar-server-dao").setQualifier(Qualifiers.MODULE); + + when(componentDao.getNullableByKey(session, project.key())).thenReturn(project); + when(componentDao.getNullableByKey(session, module.key())).thenReturn(module); + when(componentDao.getNullableByKey(session, subModule.key())).thenReturn(subModule); + when(language.getKey()).thenReturn("java"); - when(languages.all()).thenReturn(new Language[]{language}); + when(languages.all()).thenReturn(new Language[] {language}); + + when(qProfileFactory.getDefault(session, "java")).thenReturn( + QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") + ); tester = new WsTester(new BatchWs(mock(BatchIndex.class), mock(GlobalReferentialsAction.class), new ProjectReferentialsAction(dbClient, propertiesDao, qProfileFactory, qProfileLoader, ruleService, languages))); } @Test - public void return_settings_by_modules() throws Exception { + public void return_project_settings() throws Exception { MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); - String projectKey = "org.codehaus.sonar:sonar"; - String moduleKey = "org.codehaus.sonar:sonar-server"; - when(componentDao.findModulesByProject(projectKey, session)).thenReturn(newArrayList( - new ComponentDto().setKey(projectKey), - new ComponentDto().setKey(moduleKey) + // Project without modules + when(componentDao.findModulesByProject(project.key(), session)).thenReturn(Collections.<ComponentDto>emptyList()); + + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR"), + new PropertyDto().setKey("sonar.jira.login.secured").setValue("john") )); - when(propertiesDao.selectProjectProperties(projectKey, session)).thenReturn(newArrayList( + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", project.key()); + request.execute().assertJson(getClass(), "return_project_settings.json"); + } + + @Test + public void return_project_with_module_settings() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + + when(componentDao.findModulesByProject(project.key(), session)).thenReturn(newArrayList(module)); + + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR"), new PropertyDto().setKey("sonar.jira.login.secured").setValue("john") )); - when(propertiesDao.selectProjectProperties(moduleKey, session)).thenReturn(newArrayList( + when(propertiesDao.selectProjectProperties(module.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER"), new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java") - )); + )); - when(qProfileFactory.getDefault(session, "java")).thenReturn( - QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") - ); + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", project.key()); + request.execute().assertJson(getClass(), "return_project_with_module_settings.json"); + } - WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey); - request.execute().assertJson(getClass(), "return_settings_by_modules.json"); + @Test + public void return_project_with_module_settings_inherited_from_project() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + + when(componentDao.findModulesByProject(project.key(), session)).thenReturn(newArrayList(module)); + + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR"), + new PropertyDto().setKey("sonar.jira.login.secured").setValue("john") + )); + // No property on module -> should have the same than project + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", project.key()); + request.execute().assertJson(getClass(), "return_project_with_module_settings_inherited_from_project.json"); } @Test - public void return_settings_by_modules_without_empty_module_settings() throws Exception { + public void return_project_with_module_with_sub_module() throws Exception { MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); - String projectKey = "org.codehaus.sonar:sonar"; - String moduleKey = "org.codehaus.sonar:sonar-server"; - when(componentDao.findModulesByProject(projectKey, session)).thenReturn(newArrayList( - new ComponentDto().setKey(projectKey), - new ComponentDto().setKey(moduleKey) - )); + when(componentDao.findModulesByProject(project.key(), session)).thenReturn(newArrayList(module)); + when(componentDao.findModulesByProject(module.key(), session)).thenReturn(newArrayList(subModule)); - when(propertiesDao.selectProjectProperties(projectKey, session)).thenReturn(newArrayList( + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR"), new PropertyDto().setKey("sonar.jira.login.secured").setValue("john") + )); + + when(propertiesDao.selectProjectProperties(module.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER"), + new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java") + )); + + when(propertiesDao.selectProjectProperties(subModule.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER-DAO") + )); + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", project.key()); + request.execute().assertJson(getClass(), "return_project_with_module_with_sub_module.json"); + } + + @Test + public void return_sub_module_settings() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + + when(componentDao.getRootProjectByKey(subModule.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(module.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(subModule.key(), session)).thenReturn(module); + + when(propertiesDao.selectProjectProperties(subModule.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR"), + new PropertyDto().setKey("sonar.jira.login.secured").setValue("john"), + new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java") )); - // No property on module - when(qProfileFactory.getDefault(session, "java")).thenReturn( - QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") - ); + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", subModule.key()); + request.execute().assertJson(getClass(), "return_sub_module_settings.json"); + } - WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey); - request.execute().assertJson(getClass(), "return_settings_by_modules_without_empty_module_settings.json"); + @Test + public void return_sub_module_settings_including_settings_from_parent_modules() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + + when(componentDao.getRootProjectByKey(subModule.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(module.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(subModule.key(), session)).thenReturn(module); + + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR") + )); + + when(propertiesDao.selectProjectProperties(module.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.login.secured").setValue("john") + )); + + when(propertiesDao.selectProjectProperties(subModule.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java") + )); + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", subModule.key()); + request.execute().assertJson(getClass(), "return_sub_module_settings_including_settings_from_parent_modules.json"); + } + + @Test + public void return_sub_module_settings_only_inherited_from_project() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + + when(componentDao.getRootProjectByKey(subModule.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(module.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(subModule.key(), session)).thenReturn(module); + + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR"), + new PropertyDto().setKey("sonar.jira.login.secured").setValue("john"), + new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java") + )); + // No settings on module or sub module -> All setting should come from the project + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", subModule.key()); + request.execute().assertJson(getClass(), "return_sub_module_settings_inherited_from_project.json"); + } + + @Test + public void return_sub_module_settings_inherited_from_project_and_module() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + + when(componentDao.getRootProjectByKey(subModule.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(module.key(), session)).thenReturn(project); + when(componentDao.getParentModuleByKey(subModule.key(), session)).thenReturn(module); + + when(propertiesDao.selectProjectProperties(project.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.login.secured").setValue("john"), + new PropertyDto().setKey("sonar.coverage.exclusions").setValue("**/*.java") + )); + + when(propertiesDao.selectProjectProperties(module.key(), session)).thenReturn(newArrayList( + new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR-SERVER") + )); + + // No settings on sub module -> All setting should come from the project and the module + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", subModule.key()); + request.execute().assertJson(getClass(), "return_sub_module_settings_inherited_from_project_and_module.json"); } @Test @@ -154,7 +281,7 @@ public class ProjectReferentialsActionTest { when(qProfileFactory.getByProjectAndLanguage(session, projectKey, "java")).thenReturn( QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") - ); + ); WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey); request.execute().assertJson(getClass(), "return_quality_profiles.json"); @@ -168,7 +295,7 @@ public class ProjectReferentialsActionTest { try { request.execute(); - } catch (Exception e){ + } catch (Exception e) { assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("No quality profile can been found on language 'java' for project 'org.codehaus.sonar:sonar'"); } } @@ -180,7 +307,7 @@ public class ProjectReferentialsActionTest { when(qProfileFactory.getDefault(session, "java")).thenReturn( QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") - ); + ); WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey); request.execute().assertJson(getClass(), "return_quality_profile_from_default_profile.json"); @@ -193,20 +320,34 @@ public class ProjectReferentialsActionTest { when(qProfileFactory.getByNameAndLanguage(session, "Default", "java")).thenReturn( QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") - ); + ); WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey).setParam("profile", "Default"); request.execute().assertJson(getClass(), "return_quality_profile_from_given_profile_name.json"); } @Test + public void return_quality_profiles_even_when_project_does_not_exists() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + String projectKey = "org.codehaus.sonar:sonar"; + when(componentDao.getNullableByKey(session, projectKey)).thenReturn(null); + + when(qProfileFactory.getDefault(session, "java")).thenReturn( + QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") + ); + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey); + request.execute().assertJson(getClass(), "return_quality_profiles_even_when_project_does_not_exists.json"); + } + + @Test public void return_active_rules() throws Exception { MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); String projectKey = "org.codehaus.sonar:sonar"; when(qProfileFactory.getByProjectAndLanguage(session, projectKey, "java")).thenReturn( QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") - ); + ); RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); ActiveRule activeRule = mock(ActiveRule.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/persistence/ComponentDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/persistence/ComponentDaoTest.java index c5cb63a11b5..190127ecca3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/persistence/ComponentDaoTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/persistence/ComponentDaoTest.java @@ -27,6 +27,7 @@ import org.sonar.api.utils.System2; import org.sonar.core.component.ComponentDto; import org.sonar.core.persistence.AbstractDaoTestCase; import org.sonar.core.persistence.DbSession; +import org.sonar.server.exceptions.NotFoundException; import java.util.Date; import java.util.List; @@ -109,16 +110,49 @@ public class ComponentDaoTest extends AbstractDaoTestCase { @Test public void find_modules_by_project() throws Exception { - setupData("shared"); + setupData("multi-modules"); List<ComponentDto> results = dao.findModulesByProject("org.struts:struts", session); assertThat(results).hasSize(1); assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-core"); + results = dao.findModulesByProject("org.struts:struts-core", session); + assertThat(results).hasSize(1); + assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data"); + + assertThat(dao.findModulesByProject("org.struts:struts-data", session)).isEmpty(); + assertThat(dao.findModulesByProject("unknown", session)).isEmpty(); } @Test + public void get_root_project_by_key() throws Exception { + setupData("multi-modules"); + + assertThat(dao.getRootProjectByKey("org.struts:struts-data", session).getKey()).isEqualTo("org.struts:struts"); + assertThat(dao.getRootProjectByKey("org.struts:struts-core", session).getKey()).isEqualTo("org.struts:struts"); + + // Root project of a project is itself + assertThat(dao.getRootProjectByKey("org.struts:struts", session).getKey()).isEqualTo("org.struts:struts"); + } + + @Test(expected = NotFoundException.class) + public void get_root_project_by_key_on_unknown_project() throws Exception { + dao.getRootProjectByKey("unknown", session); + } + + @Test + public void get_parent_module_by_key() throws Exception { + setupData("multi-modules"); + + assertThat(dao.getParentModuleByKey("org.struts:struts-data", session).getKey()).isEqualTo("org.struts:struts-core"); + assertThat(dao.getParentModuleByKey("org.struts:struts-core", session).getKey()).isEqualTo("org.struts:struts"); + assertThat(dao.getParentModuleByKey("org.struts:struts", session)).isNull(); + + assertThat(dao.getParentModuleByKey("unknown", session)).isNull(); + } + + @Test public void insert() { when(system2.now()).thenReturn(DateUtils.parseDate("2014-06-18").getTime()); setupData("empty"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/DatabaseCheckerTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/DatabaseCheckerTest.java index 9baaac36dd3..c4c5844e68e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/DatabaseCheckerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/DatabaseCheckerTest.java @@ -62,7 +62,9 @@ public class DatabaseCheckerTest { @Test public void log_warning_if_h2() throws Exception { Database db = mockDb(new H2(), "13.4"); - new DatabaseChecker(db).start(); + DatabaseChecker checker = new DatabaseChecker(db); + checker.start(); + checker.stop(); // TODO test log } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/RulesAggregationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/RulesAggregationTest.java index 31cb3fbc9e9..7ae8476846f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/RulesAggregationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/RulesAggregationTest.java @@ -43,7 +43,7 @@ public class RulesAggregationTest { rulesAggregation.add(ruleDto); rulesAggregation.add(ruleDto); - RulesAggregation.Rule rule = new RulesAggregation.Rule().setRuleKey(ruleKey).setName("Rule name"); + RulesAggregation.Rule rule = new RulesAggregation.Rule(ruleKey, "Rule name"); assertThat(rulesAggregation.rules()).hasSize(1); assertThat(rulesAggregation.rules().iterator().next().name()).isEqualTo("Rule name"); @@ -61,4 +61,19 @@ public class RulesAggregationTest { assertThat(rulesAggregation.rules()).hasSize(2); } + + @Test + public void test_equals_and_hash_code() throws Exception { + RulesAggregation.Rule rule = new RulesAggregation.Rule(RuleKey.of("xoo", "S001"), "S001"); + RulesAggregation.Rule ruleSameRuleKey = new RulesAggregation.Rule(RuleKey.of("xoo", "S001"), "S001"); + RulesAggregation.Rule ruleWithDifferentRuleKey = new RulesAggregation.Rule(RuleKey.of("xoo", "S002"), "S002"); + + assertThat(rule).isEqualTo(rule); + assertThat(rule).isEqualTo(ruleSameRuleKey); + assertThat(rule).isNotEqualTo(ruleWithDifferentRuleKey); + + assertThat(rule.hashCode()).isEqualTo(rule.hashCode()); + assertThat(rule.hashCode()).isEqualTo(ruleSameRuleKey.hashCode()); + assertThat(rule.hashCode()).isNotEqualTo(ruleWithDifferentRuleKey.hashCode()); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/MeasureFilterFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/MeasureFilterFactoryTest.java index bd6348e7a28..20cc5872bee 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/MeasureFilterFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/MeasureFilterFactoryTest.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; @@ -90,6 +91,18 @@ public class MeasureFilterFactoryTest { } @Test + public void fallback_on_name_sort_when_sort_is_unknown() { + MeasureFilterFactory factory = new MeasureFilterFactory(newMetricFinder(), system); + Map<String, Object> props = ImmutableMap.<String, Object>of("sort", "unknown"); + MeasureFilter filter = factory.create(props); + + assertThat(filter.sort().column()).isEqualTo("p.long_name"); + assertThat(filter.sort().metric()).isNull(); + assertThat(filter.sort().period()).isNull(); + assertThat(filter.sort().isAsc()).isTrue(); + } + + @Test public void descending_sort() { MeasureFilterFactory factory = new MeasureFilterFactory(newMetricFinder(), system); Map<String, Object> props = ImmutableMap.<String, Object>of("asc", "false"); @@ -196,6 +209,39 @@ public class MeasureFilterFactoryTest { } @Test + public void name_conditions() { + MeasureFilterFactory factory = new MeasureFilterFactory(newMetricFinder(), system); + Map<String, Object> props = ImmutableMap.<String, Object>of( + "nameSearch", "SonarQube" + ); + MeasureFilter filter = factory.create(props); + + assertThat(filter.getResourceName()).isEqualTo("SonarQube"); + } + + @Test + public void not_fail_when_name_conditions_contains_array() { + MeasureFilterFactory factory = new MeasureFilterFactory(newMetricFinder(), system); + Map<String, Object> props = ImmutableMap.<String, Object>of( + "nameSearch", new String[]{"sonar", "qube"} + ); + MeasureFilter filter = factory.create(props); + + assertThat(filter.getResourceName()).isEqualTo("sonar,qube"); + } + + @Test + public void not_fail_when_name_conditions_contains_list() { + MeasureFilterFactory factory = new MeasureFilterFactory(newMetricFinder(), system); + Map<String, Object> props = ImmutableMap.<String, Object>of( + "nameSearch", newArrayList("sonar", "qube") + ); + MeasureFilter filter = factory.create(props); + + assertThat(filter.getResourceName()).isEqualTo("sonar,qube"); + } + + @Test public void ignore_partial_measure_condition() { MeasureFilterFactory factory = new MeasureFilterFactory(newMetricFinder(), system); Map<String, Object> props = ImmutableMap.<String, Object>of( diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java index 3b7fdb0ed29..27e50982d8b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java @@ -54,8 +54,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; @@ -613,7 +615,7 @@ public class RuleIndexMediumTest { // 4. get all active rules on profile result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()), + .setQProfileKey(qualityProfileDto2.getKey()), new QueryOptions()); assertThat(result.getHits()).hasSize(1); assertThat(result.getHits().get(0).name()).isEqualTo(rule1.getName()); @@ -621,7 +623,7 @@ public class RuleIndexMediumTest { } @Test - public void search_by_profile_inheritance_and_active_severities() throws InterruptedException { + public void search_by_profile_and_inheritance() throws InterruptedException { QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1(); QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2().setParentKee(QProfileTesting.XOO_P1_KEY); db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2); @@ -650,7 +652,7 @@ public class RuleIndexMediumTest { ActiveRuleDto.createFor(qualityProfileDto2, rule3) .setSeverity("BLOCKER") .setInheritance(ActiveRule.Inheritance.INHERITED.name()) - ); + ); dbSession.commit(); @@ -672,77 +674,52 @@ public class RuleIndexMediumTest { // 3. get Inherited Rules on profile1 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(0); // 4. get Inherited Rules on profile2 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(2); // 5. get Overridden Rules on profile1 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(0); // 6. get Overridden Rules on profile2 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(1); // 7. get Inherited AND Overridden Rules on profile1 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setInheritance(ImmutableSet.of( - ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setInheritance(ImmutableSet.of( + ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(0); // 8. get Inherited AND Overridden Rules on profile2 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setInheritance(ImmutableSet.of( - ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setInheritance(ImmutableSet.of( + ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); - assertThat(result.getHits()).hasSize(3); - - // 9. get rules active on profile1 with active severity BLOCKER - result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setActiveSeverities(ImmutableSet.of( - Severity.BLOCKER.toString())), - new QueryOptions() - ); - assertThat(result.getHits()).hasSize(3); - - // 10. get rules active on profile2 with active severity MINOR, then BLOCKER + MINOR - result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setActiveSeverities(ImmutableSet.of( - Severity.MINOR.toString())), - new QueryOptions() - ); - assertThat(result.getHits()).hasSize(1); - result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setActiveSeverities(ImmutableSet.of( - Severity.BLOCKER.toString(), Severity.MINOR.toString())), - new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(3); } @@ -956,6 +933,19 @@ public class RuleIndexMediumTest { } @Test + public void scroll_byIds() throws Exception { + Set<Integer> ids = new HashSet<Integer>(); + for (int i = 0; i < 150; i++) { + RuleDto rule = RuleTesting.newDto(RuleKey.of("scroll", "r_" + i)); + dao.insert(dbSession, rule); + dbSession.commit(); + ids.add(rule.getId()); + } + List<Rule> rules = index.getByIds(ids); + assertThat(rules).hasSize(ids.size()); + } + + @Test public void search_protected_chars() throws InterruptedException { String nameWithProtectedChars = "ja#va&sc\"r:ipt"; diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java index b1bef3250f1..480382fae23 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java +++ b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java @@ -24,7 +24,6 @@ import com.google.common.collect.Lists; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.junit.rules.ExternalResource; -import org.sonar.api.CoreProperties; import org.sonar.api.database.DatabaseProperties; import org.sonar.server.platform.Platform; import org.sonar.server.search.IndexProperties; diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_settings_by_modules_without_empty_module_settings.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_settings.json index 4b562891c28..4b562891c28 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_settings_by_modules_without_empty_module_settings.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_settings.json diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_settings_by_modules.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_settings.json index a8e063a344b..d1b864aa7c4 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_settings_by_modules.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_settings.json @@ -15,6 +15,8 @@ "sonar.jira.login.secured": "john" }, "org.codehaus.sonar:sonar-server": { + "sonar.jira.project.key": "SONAR-SERVER", + "sonar.jira.login.secured": "john", "sonar.coverage.exclusions": "**/*.java" } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_settings_inherited_from_project.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_settings_inherited_from_project.json new file mode 100644 index 00000000000..deaea79e1a3 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_settings_inherited_from_project.json @@ -0,0 +1,22 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": { + "org.codehaus.sonar:sonar": { + "sonar.jira.project.key": "SONAR", + "sonar.jira.login.secured": "john" + }, + "org.codehaus.sonar:sonar-server": { + "sonar.jira.project.key": "SONAR", + "sonar.jira.login.secured": "john" + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_with_sub_module.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_with_sub_module.json new file mode 100644 index 00000000000..ef8ef13fa12 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_project_with_module_with_sub_module.json @@ -0,0 +1,28 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": { + "org.codehaus.sonar:sonar": { + "sonar.jira.project.key": "SONAR", + "sonar.jira.login.secured": "john" + }, + "org.codehaus.sonar:sonar-server": { + "sonar.jira.project.key": "SONAR-SERVER", + "sonar.jira.login.secured": "john", + "sonar.coverage.exclusions": "**/*.java" + }, + "org.codehaus.sonar:sonar-server-dao": { + "sonar.jira.project.key": "SONAR-SERVER-DAO", + "sonar.jira.login.secured": "john", + "sonar.coverage.exclusions": "**/*.java" + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_quality_profiles_even_when_project_does_not_exists.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_quality_profiles_even_when_project_does_not_exists.json new file mode 100644 index 00000000000..a1bb33bb01f --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_quality_profiles_even_when_project_does_not_exists.json @@ -0,0 +1,13 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": {} +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings.json new file mode 100644 index 00000000000..f2a89a9895a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings.json @@ -0,0 +1,19 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": { + "org.codehaus.sonar:sonar-server-dao": { + "sonar.jira.project.key": "SONAR", + "sonar.jira.login.secured": "john", + "sonar.coverage.exclusions": "**/*.java" + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_including_settings_from_parent_modules.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_including_settings_from_parent_modules.json new file mode 100644 index 00000000000..f2a89a9895a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_including_settings_from_parent_modules.json @@ -0,0 +1,19 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": { + "org.codehaus.sonar:sonar-server-dao": { + "sonar.jira.project.key": "SONAR", + "sonar.jira.login.secured": "john", + "sonar.coverage.exclusions": "**/*.java" + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_inherited_from_project.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_inherited_from_project.json new file mode 100644 index 00000000000..f2a89a9895a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_inherited_from_project.json @@ -0,0 +1,19 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": { + "org.codehaus.sonar:sonar-server-dao": { + "sonar.jira.project.key": "SONAR", + "sonar.jira.login.secured": "john", + "sonar.coverage.exclusions": "**/*.java" + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_inherited_from_project_and_module.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_inherited_from_project_and_module.json new file mode 100644 index 00000000000..c99fd8e67db --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_sub_module_settings_inherited_from_project_and_module.json @@ -0,0 +1,19 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [], + "settingsByModule": { + "org.codehaus.sonar:sonar-server-dao": { + "sonar.jira.project.key": "SONAR-SERVER", + "sonar.jira.login.secured": "john", + "sonar.coverage.exclusions": "**/*.java" + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/persistence/ComponentDaoTest/multi-modules.xml b/server/sonar-server/src/test/resources/org/sonar/server/component/persistence/ComponentDaoTest/multi-modules.xml new file mode 100644 index 00000000000..decdab5279a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/persistence/ComponentDaoTest/multi-modules.xml @@ -0,0 +1,89 @@ +<dataset> + + <!-- Struts projects is authorized for all user --> + <group_roles id="1" group_id="[null]" resource_id="1" role="user"/> + + + <!-- root project --> + <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts" + description="the description" long_name="Apache Struts" + enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]"/> + <snapshots id="1" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]" + status="P" islast="[true]" purge_status="[null]" + period1_mode="[null]" period1_param="[null]" period1_date="[null]" + period2_mode="[null]" period2_param="[null]" period2_date="[null]" + period3_mode="[null]" period3_param="[null]" period3_date="[null]" + period4_mode="[null]" period4_param="[null]" period4_date="[null]" + period5_mode="[null]" period5_param="[null]" period5_date="[null]" + depth="[null]" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" build_date="2008-12-02 13:58:00.00" + version="[null]" path=""/> + <snapshots id="10" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]" + status="P" islast="[false]" purge_status="[null]" + period1_mode="[null]" period1_param="[null]" period1_date="[null]" + period2_mode="[null]" period2_param="[null]" period2_date="[null]" + period3_mode="[null]" period3_param="[null]" period3_date="[null]" + period4_mode="[null]" period4_param="[null]" period4_date="[null]" + period5_mode="[null]" period5_param="[null]" period5_date="[null]" + depth="[null]" scope="PRJ" qualifier="TRK" created_at="2008-12-01 13:58:00.00" build_date="2008-12-01 13:58:00.00" + version="[null]" path=""/> + + <!-- module --> + <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core" + scope="PRJ" qualifier="BRC" long_name="Struts Core" + description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]"/> + <snapshots id="2" project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1" + status="P" islast="[true]" purge_status="[null]" + period1_mode="[null]" period1_param="[null]" period1_date="[null]" + period2_mode="[null]" period2_param="[null]" period2_date="[null]" + period3_mode="[null]" period3_param="[null]" period3_date="[null]" + period4_mode="[null]" period4_param="[null]" period4_date="[null]" + period5_mode="[null]" period5_param="[null]" period5_date="[null]" + depth="[null]" scope="PRJ" qualifier="BRC" created_at="2008-12-02 13:58:00.00" build_date="2008-12-02 13:58:00.00" + version="[null]" path="1."/> + + <!-- sub module --> + <projects id="3" root_id="1" kee="org.struts:struts-data" name="Struts Data" + scope="PRJ" qualifier="BRC" long_name="Struts Data" + description="[null]" enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]"/> + <snapshots id="3" project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1" + status="P" islast="[true]" purge_status="[null]" + period1_mode="[null]" period1_param="[null]" period1_date="[null]" + period2_mode="[null]" period2_param="[null]" period2_date="[null]" + period3_mode="[null]" period3_param="[null]" period3_date="[null]" + period4_mode="[null]" period4_param="[null]" period4_date="[null]" + period5_mode="[null]" period5_param="[null]" period5_date="[null]" + depth="[null]" scope="PRJ" qualifier="BRC" created_at="2008-12-02 13:58:00.00" build_date="2008-12-02 13:58:00.00" + version="[null]" path="1.2."/> + + <!-- directory --> + <projects long_name="org.struts" id="4" scope="DIR" qualifier="DIR" kee="org.struts:struts-core:src/org/struts" + name="src/org/struts" root_id="3" + description="[null]" + enabled="[true]" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/org/struts"/> + <snapshots id="4" project_id="4" parent_snapshot_id="3" root_project_id="1" root_snapshot_id="1" + status="P" islast="[true]" purge_status="[null]" + period1_mode="[null]" period1_param="[null]" period1_date="[null]" + period2_mode="[null]" period2_param="[null]" period2_date="[null]" + period3_mode="[null]" period3_param="[null]" period3_date="[null]" + period4_mode="[null]" period4_param="[null]" period4_date="[null]" + period5_mode="[null]" period5_param="[null]" period5_date="[null]" + depth="[null]" scope="DIR" qualifier="PAC" created_at="2008-12-02 13:58:00.00" build_date="2008-12-02 13:58:00.00" + version="[null]" path="1.2.3."/> + + <!-- file --> + <projects long_name="org.struts.RequestContext" id="5" scope="FIL" qualifier="FIL" kee="org.struts:struts-core:src/org/struts/RequestContext.java" + name="RequestContext.java" root_id="3" + description="[null]" + enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]" path="src/org/struts/RequestContext.java"/> + + <snapshots id="5" project_id="5" parent_snapshot_id="4" root_project_id="1" root_snapshot_id="1" + status="P" islast="[true]" purge_status="[null]" + period1_mode="[null]" period1_param="[null]" period1_date="[null]" + period2_mode="[null]" period2_param="[null]" period2_date="[null]" + period3_mode="[null]" period3_param="[null]" period3_date="[null]" + period4_mode="[null]" period4_param="[null]" period4_date="[null]" + period5_mode="[null]" period5_param="[null]" period5_date="[null]" + depth="[null]" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" build_date="2008-12-02 13:58:00.00" + version="[null]" path="1.2.3.4."/> + +</dataset> diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index 9fd22feb5db..f905e6201af 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -81,6 +81,7 @@ module.exports = (grunt) -> '<%= pkg.assets %>js/third-party/underscore.js' '<%= pkg.assets %>js/third-party/select2.js' '<%= pkg.assets %>js/third-party/keymaster.js' + '<%= pkg.assets %>js/third-party/moment.js' '<%= pkg.assets %>js/select2-jquery-ui-fix.js' '<%= pkg.assets %>js/widgets/base.js' '<%= pkg.assets %>js/widgets/widget.js' @@ -113,6 +114,7 @@ module.exports = (grunt) -> '<%= pkg.assets %>js/third-party/underscore.js' '<%= pkg.assets %>js/third-party/select2.js' '<%= pkg.assets %>js/third-party/keymaster.js' + '<%= pkg.assets %>js/third-party/moment.js' '<%= pkg.assets %>js/select2-jquery-ui-fix.js' '<%= pkg.assets %>js/widgets/base.js' '<%= pkg.assets %>js/widgets/widget.js' diff --git a/server/sonar-web/pom.xml b/server/sonar-web/pom.xml index 6aec8f63f33..7e51b5c2a76 100644 --- a/server/sonar-web/pom.xml +++ b/server/sonar-web/pom.xml @@ -351,7 +351,6 @@ <sonar.log.console>true</sonar.log.console> <sonar.log.profilingLevel>BASIC</sonar.log.profilingLevel> <sonar.web.context>/dev</sonar.web.context> - <sonar.updatecenter.activate>false</sonar.updatecenter.activate> </systemProperties> </configuration> </plugin> diff --git a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee index 22204f2929a..b0bc3653b67 100644 --- a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee +++ b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee @@ -24,7 +24,8 @@ define [ that = @ @options.collection.each (facet) -> property = facet.get 'property' - facet.set 'property_message', 'coding_rules.facets.' + property + facet.set 'property_message', t 'coding_rules.facets.' + property + facet.set 'limitReached', facet.get('values').length >= 10 _.each(facet.get('values'), (value) -> value.text = that.options.app.facetLabel(property, value.val) ) diff --git a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-list-item-view.coffee b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-list-item-view.coffee index 77efec04259..0c77a630538 100644 --- a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-list-item-view.coffee +++ b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-list-item-view.coffee @@ -24,5 +24,8 @@ define [ serializeData: -> + tags = _.union @model.get('sysTags'), @model.get('tags') _.extend super, - allTags: _.union @model.get('sysTags'), @model.get('tags') + manualRuleLabel: t 'coding_rules.manual_rule' + allTags: tags + showDetails: (@model.get('status') != 'READY') || (_.isArray(tags) && tags.length > 0) diff --git a/server/sonar-web/src/main/coffee/coding-rules/views/filters/tag-filter-view.coffee b/server/sonar-web/src/main/coffee/coding-rules/views/filters/tag-filter-view.coffee index a6b33b8a05e..01366e7f2e4 100644 --- a/server/sonar-web/src/main/coffee/coding-rules/views/filters/tag-filter-view.coffee +++ b/server/sonar-web/src/main/coffee/coding-rules/views/filters/tag-filter-view.coffee @@ -15,20 +15,21 @@ define [ loadTags: -> tagsXHR = jQuery.ajax url: "#{baseUrl}/api/rules/tags" + async: false - that = @ - jQuery.when(tagsXHR).done (r) -> - that.choices = new Backbone.Collection( + jQuery.when(tagsXHR).done (r) => + @choices = new Backbone.Collection( _.map(r.tags, (tag) -> new Backbone.Model id: tag text: tag - )) + ), + comparator: 'text') - if that.tagToRestore - that.restore(that.tagToRestore) - that.tagToRestore = null - that.render() + if @tagToRestore + @restore(@tagToRestore) + @tagToRestore = null + @render() restore: (value) -> unless @choices.isEmpty() diff --git a/server/sonar-web/src/main/coffee/component-viewer/duplication-popup.coffee b/server/sonar-web/src/main/coffee/component-viewer/duplication-popup.coffee index c9e6545fd24..09aa6aa7e0b 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/duplication-popup.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/duplication-popup.coffee @@ -36,7 +36,7 @@ define [ key = $(e.currentTarget).data 'key' line = $(e.currentTarget).data 'line' files = @options.main.source.get('duplicationFiles') - @options.main.addTransition 'duplication', @collection.map (item) -> + options = @collection.map (item) -> file = files[item.get('_ref')] x = utils.splitLongName file.name key: file.key @@ -46,6 +46,8 @@ define [ projectName: file.projectName subProjectName: file.subProjectName active: file.key == key + options = _.uniq options, (item) -> item.key + @options.main.addTransition 'duplication', options if key == @options.main.component.get 'key' @options.main.scrollToLine line @options.main.workspaceView.render() diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/tests-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/tests-header.coffee index 76e196c66c7..667796b9bb6 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header/tests-header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header/tests-header.coffee @@ -36,7 +36,12 @@ define [ onRender: -> @header.enableUnitTest = (testName) => - @ui.unitTests.filter("[data-name=#{testName}]").click() + test = @ui.unitTests.filter("[data-name=#{testName}]") + container = test.closest '.component-viewer-header-expanded-bar-section-list' + topOffset = test.offset().top - container.offset().top + if topOffset > container.height() + container.scrollTop topOffset + test.click() @$(@activeSort).addClass 'active-link' if @activeSort diff --git a/server/sonar-web/src/main/coffee/component-viewer/main.coffee b/server/sonar-web/src/main/coffee/component-viewer/main.coffee index 6dd645d853b..9f65306e531 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/main.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/main.coffee @@ -135,7 +135,7 @@ define [ availableHeight = height - @$(@headerRegion.$el).outerHeight(true) source.removeClass 'overflow' - source.width(availableWidth).css('max-height', availableHeight) + source.width(availableWidth).height availableHeight source.addClass 'overflow' workspace.removeClass 'overflow' workspace.height availableHeight diff --git a/server/sonar-web/src/main/coffee/component-viewer/source.coffee b/server/sonar-web/src/main/coffee/component-viewer/source.coffee index f0c17c7a127..bad93f7bc93 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/source.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/source.coffee @@ -158,6 +158,7 @@ define [ row = $(e.currentTarget).closest('.row') highlighted = row.is ".#{HIGHLIGHTED_ROW_CLASS}" @$(".#{HIGHLIGHTED_ROW_CLASS}").removeClass HIGHLIGHTED_ROW_CLASS + @highlightedLine = null unless highlighted row.addClass HIGHLIGHTED_ROW_CLASS @highlightedLine = row.data 'line-number' @@ -170,9 +171,10 @@ define [ highlightUsages: (e) -> + highlighted = $(e.currentTarget).is '.highlighted' key = e.currentTarget.className.split(/\s+/)[0] @$('.sym.highlighted').removeClass 'highlighted' - @$(".sym.#{key}").addClass 'highlighted' + @$(".sym.#{key}").addClass 'highlighted' unless highlighted toggleSettings: -> diff --git a/server/sonar-web/src/main/coffee/drilldown/app.coffee b/server/sonar-web/src/main/coffee/drilldown/app.coffee index c1179e3e05d..860b9b3d985 100644 --- a/server/sonar-web/src/main/coffee/drilldown/app.coffee +++ b/server/sonar-web/src/main/coffee/drilldown/app.coffee @@ -84,7 +84,9 @@ requirejs [ viewer.render() else viewer.showAllLines() - viewer.open(key).done -> + viewer.open key + viewer.on 'loaded', -> + viewer.off 'loaded' if activeHeaderTab? viewer.headerView.enableBar(activeHeaderTab).done -> f() else f() diff --git a/server/sonar-web/src/main/coffee/widgets/base.coffee b/server/sonar-web/src/main/coffee/widgets/base.coffee index 49103c0e7ca..53b66bcba4d 100644 --- a/server/sonar-web/src/main/coffee/widgets/base.coffee +++ b/server/sonar-web/src/main/coffee/widgets/base.coffee @@ -1,3 +1,6 @@ +moment.lang window.pageLang + + window.SonarWidgets ?= {} class BaseWidget diff --git a/server/sonar-web/src/main/coffee/widgets/treemap.coffee b/server/sonar-web/src/main/coffee/widgets/treemap.coffee index f205fb1c486..72cff1254f3 100644 --- a/server/sonar-web/src/main/coffee/widgets/treemap.coffee +++ b/server/sonar-web/src/main/coffee/widgets/treemap.coffee @@ -101,7 +101,6 @@ class Treemap extends window.SonarWidgets.BaseWidget breadcrumbsEnter.append('i').attr 'class', (d) -> if d.qualifier? then "icon-qualifier-#{d.qualifier.toLowerCase()}" else '' breadcrumbsEnterLinks = breadcrumbsEnter.append 'a' - breadcrumbsEnterLinks.classed 'underlined-link', (d, i) -> i > 0 breadcrumbsEnterLinks.html (d) -> d.name breadcrumbsEnterLinks.on 'click', (d) => @updateTreemap d.components, d.maxResultsReached diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs index 2bb6a7593d4..d2ea9d4fd56 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs @@ -4,7 +4,13 @@ {{/unless}} {{#each items}} <div class="navigator-facets-list-item" data-property="{{property}}"> - <div class="navigator-facets-list-item-name">{{t property_message}}</div> + <div class="navigator-facets-list-item-name"> + {{#if limitReached}} + {{tp 'coding_rules.facets.top' property_message}} + {{else}} + {{property_message}} + {{/if}} + </div> <div class="navigator-facets-list-item-options"> {{#each values}} {{#if count}} diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-list-item.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-list-item.hbs index ea2a2057d7e..afa53e8d33d 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-list-item.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-list-item.hbs @@ -1,13 +1,18 @@ <div class="line line-small"> - <span class="coding-rules-detail-status">{{language}}</span> + <span class="coding-rules-detail-status">{{default language manualRuleLabel}}</span> - <div class="line-right"> - <span class="coding-rules-list-tags"> - <i class="icon-tags"></i> - <span>{{#if allTags}}{{join allTags ', '}}{{else}}{{t 'coding_rules.no_tags'}}{{/if}}</span> - </span> + {{#if showDetails}} + <div class="line-right"> + {{#if allTags}} + <span class="coding-rules-list-tags"> + <i class="icon-tags"></i> + <span>{{join allTags ', '}}</span> + </span> + + {{/if}} - {{#notEq status 'READY'}}| <span class="coding-rules-detail-not-ready">{{status}}</span>{{/notEq}} - </div> + {{#notEq status 'READY'}}<span class="coding-rules-detail-not-ready">{{status}}</span>{{/notEq}} + </div> + {{/if}} </div> <div class="line" title="{{name}}" name="{{key}}">{{name}}</div> diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-profile-filter-detail.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-profile-filter-detail.hbs index ee479733dc0..3a3b44cff8b 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-profile-filter-detail.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-profile-filter-detail.hbs @@ -1,10 +1,16 @@ <li> <label title="{{id}}" data-id="{{id}}"> - <i class="icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}} {{#unless multiple}}icon-checkbox-single{{/unless}}"></i> <span> + <i class="icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}} {{#unless multiple}}icon-checkbox-single{{/unless}}"></i> {{text}} - <br> - <span class="subtitle">{{language}}</span> </span> + + {{#if language}} + <br> + <span> + <i class="icon-checkbox icon-checkbox-invisible"></i> + <span class="subtitle">{{language}}</span> + </span> + {{/if}} </label> </li> diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-repository-detail.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-repository-detail.hbs index 081660e3b5c..3a3b44cff8b 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-repository-detail.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-repository-detail.hbs @@ -1,12 +1,16 @@ <li> <label title="{{id}}" data-id="{{id}}"> - <i class="icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}} {{#unless multiple}}icon-checkbox-single{{/unless}}"></i> <span> + <i class="icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}} {{#unless multiple}}icon-checkbox-single{{/unless}}"></i> {{text}} - {{#if language}} - <br> - <span class="subtitle">{{language}}</span> - {{/if}} </span> + + {{#if language}} + <br> + <span> + <i class="icon-checkbox icon-checkbox-invisible"></i> + <span class="subtitle">{{language}}</span> + </span> + {{/if}} </label> </li> diff --git a/server/sonar-web/src/main/hbs/common/_markdown-tips.hbs b/server/sonar-web/src/main/hbs/common/_markdown-tips.hbs index fe5edc9fc35..51ebaa00ae5 100644 --- a/server/sonar-web/src/main/hbs/common/_markdown-tips.hbs +++ b/server/sonar-web/src/main/hbs/common/_markdown-tips.hbs @@ -1,4 +1,4 @@ <div class="markdown-tips"> - <a href="#" class="underlined-link" onclick="window.open(baseUrl + '/markdown/help','markdown','height=300,width=600,scrollbars=1,resizable=1');return false;">{{t 'markdown.helplink'}}</a> : + <a href="#" onclick="window.open(baseUrl + '/markdown/help','markdown','height=300,width=600,scrollbars=1,resizable=1');return false;">{{t 'markdown.helplink'}}</a> : *{{t 'bold'}}* ``{{t 'code'}}`` * {{t 'bulleted_point'}} </div> diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-duplication-popup.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-duplication-popup.hbs index 8ae13b172a6..bf134d7fd62 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/cw-duplication-popup.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/cw-duplication-popup.hbs @@ -2,19 +2,11 @@ <div class="bubble-popup-title">{{t 'component_viewer.transition.duplication'}}</div> {{#each duplications}} <div class="bubble-popup-section"> - {{#notEq file.project ../component.project}} - <div class="component-viewer-popup-label"> - <i class="icon-cross-project"></i> - {{file.projectName}} - {{#if file.subProjectName}} - / {{file.subProjectName}} - {{/if}} + {{#notEqComponents file ../component}} + <div class="component-viewer-popup-label" title="{{projectFullName file}}"> + <i class="icon-qualifier-trk"></i> {{projectFullName file}} </div> - {{else}} - {{#notEq file.subProject ../../component.subProject}} - <div class="component-viewer-popup-label">{{file.projectName}} / {{file.subProjectName}}</div> - {{/notEq}} - {{/notEq}} + {{/notEqComponents}} {{#notEq file.key ../component.key}} <a class="link-action" data-key="{{file.key}}" title="{{file.name}}"> diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-more-actions.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-more-actions.hbs index 354436d5cdb..4fe8ed7400c 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/cw-more-actions.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/cw-more-actions.hbs @@ -1,10 +1,10 @@ -<a class="js-new-window underlined-link">{{t 'component_viewer.new_window'}}</a> +<a class="js-new-window">{{t 'component_viewer.new_window'}}</a> <br> -<a class="js-full-source underlined-link">{{t 'component_viewer.show_full_source'}}</a> +<a class="js-full-source">{{t 'component_viewer.show_full_source'}}</a> <br> -<a class="js-raw-source underlined-link">{{t 'component_viewer.show_raw_source'}}</a> +<a class="js-raw-source">{{t 'component_viewer.show_raw_source'}}</a> {{#each state.extensions}} <br> - <a class="js-extension underlined-link" data-key="{{this.[0]}}">{{this.[1]}}</a> -{{/each}}
\ No newline at end of file + <a class="js-extension" data-key="{{this.[0]}}">{{this.[1]}}</a> +{{/each}} diff --git a/server/sonar-web/src/main/hbs/component-viewer/header/cw-basic-header.hbs b/server/sonar-web/src/main/hbs/component-viewer/header/cw-basic-header.hbs index 32cd2c1730f..b108d744d00 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/header/cw-basic-header.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/header/cw-basic-header.hbs @@ -5,7 +5,7 @@ </div> <ul class="component-viewer-header-expanded-bar-section-list"> {{{componentViewerHeaderLink lines 'lines' 'js-filter-lines'}}} - {{#if 'ncloc_data'}} + {{#if ncloc_data}} {{{componentViewerHeaderLink ncloc 'ncloc' 'js-filter-ncloc'}}} {{else}} {{{componentViewerHeaderItem ncloc 'ncloc'}}} diff --git a/server/sonar-web/src/main/hbs/component-viewer/header/cw-coverage-header.hbs b/server/sonar-web/src/main/hbs/component-viewer/header/cw-coverage-header.hbs index 8b656730ef7..9b0966ce4c3 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/header/cw-coverage-header.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/header/cw-coverage-header.hbs @@ -1,6 +1,6 @@ {{#if state.hasSource}} <div class="component-viewer-header-time-changes"> - <a class="highlighted-link js-coverage-time-changes"> + <a class="js-coverage-time-changes"> {{#if period}}Δ {{period.label}}{{else}}<i class="icon-period"></i> {{t 'component_viewer.time_changes'}}{{/if}} </a> </div> diff --git a/server/sonar-web/src/main/hbs/component-viewer/header/cw-scm-header.hbs b/server/sonar-web/src/main/hbs/component-viewer/header/cw-scm-header.hbs index 599f9f71e33..2cf3da9e995 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/header/cw-scm-header.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/header/cw-scm-header.hbs @@ -1,5 +1,5 @@ <div class="component-viewer-header-time-changes"> - <a class="highlighted-link js-scm-time-changes"> + <a class="js-scm-time-changes"> {{#if period}}Δ {{period.label}}{{else}}<i class="icon-period"></i> {{t 'component_viewer.time_changes'}}{{/if}} </a> </div> diff --git a/server/sonar-web/src/main/hbs/component-viewer/header/cw-tests-header.hbs b/server/sonar-web/src/main/hbs/component-viewer/header/cw-tests-header.hbs index 19f904c1b3b..c11d69bc279 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/header/cw-tests-header.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/header/cw-tests-header.hbs @@ -47,9 +47,9 @@ <span class="ib"> {{t 'component_viewer.measure_section.test_cases'}} {{t 'component_viewer.tests.ordered_by'}} - <a class="js-sort-tests-name underlined-link">{{t 'component_viewer.tests.test_name'}}</a> + <a class="js-sort-tests-name">{{t 'component_viewer.tests.test_name'}}</a> / - <a class="js-sort-tests-duration underlined-link">{{t 'component_viewer.tests.duration'}}</a> + <a class="js-sort-tests-duration">{{t 'component_viewer.tests.duration'}}</a> </span> {{#if hasCoveragePerTestData}} <span class="ib">{{t 'component_viewer.covered_lines'}}</span> diff --git a/server/sonar-web/src/main/hbs/issue/issue.hbs b/server/sonar-web/src/main/hbs/issue/issue.hbs index ac3a80fd69e..de7379aa2c5 100644 --- a/server/sonar-web/src/main/hbs/issue/issue.hbs +++ b/server/sonar-web/src/main/hbs/issue/issue.hbs @@ -105,10 +105,10 @@ <div class="code-issue-details"> <ul class="code-issue-tabs"> <li> - <a class="js-tab-link underlined-link" href="#tab-issue-rule">{{t 'rule'}}</a> + <a class="js-tab-link" href="#tab-issue-rule">{{t 'rule'}}</a> </li> <li> - <a class="js-tab-link underlined-link" href="#tab-issue-changelog">{{t 'changelog'}}</a> + <a class="js-tab-link" href="#tab-issue-changelog">{{t 'changelog'}}</a> </li> </ul> diff --git a/server/sonar-web/src/main/hbs/navigator/choice-filter-item.hbs b/server/sonar-web/src/main/hbs/navigator/choice-filter-item.hbs index c8b4b596a90..d5fed7a743f 100644 --- a/server/sonar-web/src/main/hbs/navigator/choice-filter-item.hbs +++ b/server/sonar-web/src/main/hbs/navigator/choice-filter-item.hbs @@ -1,13 +1,16 @@ <li> <label title="{{text}}" data-id="{{id}}"> - <i class="icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}} {{#unless multiple}}icon-checkbox-single{{/unless}}"></i> - {{#if icon}}<i class="icon-{{icon}}"></i>{{/if}} <span> - {{text}} - {{#if category}} - <br> - <span class="subtitle">{{category}}</span> - {{/if}} + <i class="icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}} {{#unless multiple}}icon-checkbox-single{{/unless}}"></i> + {{#if icon}}<i class="icon-{{icon}}"></i>{{/if}} + {{text}} + </span> + {{#if category}} + <br> + <span> + <i class="icon-checkbox icon-checkbox-invisible"></i> + <span class="subtitle">{{category}}</span> </span> + {{/if}} </label> </li> diff --git a/server/sonar-web/src/main/hbs/navigator/more-criteria-details-filter.hbs b/server/sonar-web/src/main/hbs/navigator/more-criteria-details-filter.hbs index 252cacc2476..c56b0e7023d 100644 --- a/server/sonar-web/src/main/hbs/navigator/more-criteria-details-filter.hbs +++ b/server/sonar-web/src/main/hbs/navigator/more-criteria-details-filter.hbs @@ -1,7 +1,7 @@ <ul class="navigator-filter-select-list"> {{#each filters}} <li> - <label data-id="{{id}}" {{#if inactive}}class="inactive"{{/if}} {{#if title}}title="{{title}}"{{/if}}> + <label data-id="{{id}}" data-property="{{property}}" {{#if inactive}}class="inactive"{{/if}} {{#if title}}title="{{title}}"{{/if}}> {{name}} </label> </li> diff --git a/server/sonar-web/src/main/js/application.js b/server/sonar-web/src/main/js/application.js index 730edbfc0e0..c17aeadeaea 100644 --- a/server/sonar-web/src/main/js/application.js +++ b/server/sonar-web/src/main/js/application.js @@ -58,7 +58,7 @@ function resourceViewerOnBulkIssues() { } } else { // No tab selected, see how to add tab parameter - if (window.location.search.startsWith('?')) { + if (window.location.search.indexOf('?') === 0) { window.location.search += ('&' + issuesTab); } else { window.location.search += ('?' + issuesTab); diff --git a/server/sonar-web/src/main/js/common/handlebars-extensions.js b/server/sonar-web/src/main/js/common/handlebars-extensions.js index cf16e5968ca..7846b606990 100644 --- a/server/sonar-web/src/main/js/common/handlebars-extensions.js +++ b/server/sonar-web/src/main/js/common/handlebars-extensions.js @@ -1,20 +1,16 @@ requirejs.config({ paths: { - 'moment': 'third-party/moment', 'handlebars': 'third-party/handlebars' }, shim: { - 'moment': { - exports: 'moment' - }, 'handlebars': { exports: 'Handlebars' } } }); -define(['handlebars', 'moment'], function (Handlebars, moment) { +define(['handlebars'], function (Handlebars) { /* * Shortcut for templates retrieving @@ -341,4 +337,19 @@ define(['handlebars', 'moment'], function (Handlebars, moment) { } }); + Handlebars.registerHelper('eqComponents', function (a, b, options) { + var notEq = a && b && ((a.project !== b.project) || (a.subProject !== b.subProject)); + return notEq ? options.inverse(this) : options.fn(this); + }); + + Handlebars.registerHelper('notEqComponents', function (a, b, options) { + var notEq = a && b && ((a.project !== b.project) || (a.subProject !== b.subProject)); + return notEq ? options.fn(this) : options.inverse(this); + }); + + Handlebars.registerHelper('projectFullName', function (component) { + var name = component.projectName + (component.subProjectName ? (' / ' + component.subProjectName) : ''); + return name; + }); + }); diff --git a/server/sonar-web/src/main/js/issues/app.js b/server/sonar-web/src/main/js/issues/app.js index 31715516a2d..e299af9b820 100644 --- a/server/sonar-web/src/main/js/issues/app.js +++ b/server/sonar-web/src/main/js/issues/app.js @@ -4,8 +4,7 @@ requirejs.config({ paths: { 'backbone': 'third-party/backbone', 'backbone.marionette': 'third-party/backbone.marionette', - 'handlebars': 'third-party/handlebars', - 'moment': 'third-party/moment' + 'handlebars': 'third-party/handlebars' }, shim: { @@ -18,9 +17,6 @@ requirejs.config({ }, 'handlebars': { exports: 'Handlebars' - }, - 'moment': { - exports: 'moment' } } @@ -28,7 +24,7 @@ requirejs.config({ requirejs( [ - 'backbone', 'backbone.marionette', 'handlebars', 'moment', + 'backbone', 'backbone.marionette', 'handlebars', 'issues/extra', 'navigator/filters/filter-bar', 'navigator/filters/base-filters', @@ -44,7 +40,7 @@ requirejs( 'common/handlebars-extensions' ], - function (Backbone, Marionette, Handlebars, moment, Extra, FilterBar, BaseFilters, CheckboxFilterView, + function (Backbone, Marionette, Handlebars, Extra, FilterBar, BaseFilters, CheckboxFilterView, ChoiceFilters, AjaxSelectFilters, FavoriteFilters, RangeFilters, ContextFilterView, ReadOnlyFilterView, ActionPlanFilterView, RuleFilterView) { Handlebars.registerPartial('detailInnerTemplate', jQuery('#issue-detail-inner-template').html()); @@ -111,6 +107,15 @@ requirejs( }); this.filters.add(projectFilter); + var assigneeChoices = { + '!assigned': window.SS.phrases.unassigned + }, + reporterChoices = {}; + if (window.SS.currentUser) { + assigneeChoices[window.SS.currentUser] = window.SS.currentUserName + ' (' + window.SS.currentUser + ')'; + reporterChoices[window.SS.currentUser] = window.SS.currentUserName + ' (' + window.SS.currentUser + ')'; + } + this.filters.add([ new BaseFilters.Filter({ name: window.SS.phrases.severity, @@ -162,9 +167,7 @@ requirejs( type: AjaxSelectFilters.AssigneeFilterView, enabled: true, optional: false, - choices: { - '!assigned': window.SS.phrases.unassigned - } + choices: assigneeChoices }), new BaseFilters.Filter({ @@ -226,7 +229,8 @@ requirejs( property: 'reporters', type: AjaxSelectFilters.ReporterFilterView, enabled: false, - optional: true + optional: true, + choices: reporterChoices }), new BaseFilters.Filter({ diff --git a/server/sonar-web/src/main/js/navigator/filters/ajax-select-filters.js b/server/sonar-web/src/main/js/navigator/filters/ajax-select-filters.js index d32cf80bdf6..4037f7172b9 100644 --- a/server/sonar-web/src/main/js/navigator/filters/ajax-select-filters.js +++ b/server/sonar-web/src/main/js/navigator/filters/ajax-select-filters.js @@ -141,7 +141,9 @@ define([ that.options.filterView.choices.unshift(item); }); _.each(that.model.get('choices'), function(v, k) { - that.options.filterView.choices.add(new Backbone.Model({ id: k, text: v })); + if (k[0] === '!') { + that.options.filterView.choices.add(new Backbone.Model({ id: k, text: v })); + } }); that.updateLists(); that.$el.removeClass('fetching'); diff --git a/server/sonar-web/src/main/js/navigator/filters/base-filters.js b/server/sonar-web/src/main/js/navigator/filters/base-filters.js index abd81862283..b73a08e0928 100644 --- a/server/sonar-web/src/main/js/navigator/filters/base-filters.js +++ b/server/sonar-web/src/main/js/navigator/filters/base-filters.js @@ -104,6 +104,7 @@ define([ var title = this.model.get('name') + ': ' + this.renderValue(); this.$el.prop('title', title); + this.$el.attr('data-property', this.model.get('property')); }, diff --git a/server/sonar-web/src/main/js/navigator/filters/choice-filters.js b/server/sonar-web/src/main/js/navigator/filters/choice-filters.js index b4553847159..c36e42ad2a7 100644 --- a/server/sonar-web/src/main/js/navigator/filters/choice-filters.js +++ b/server/sonar-web/src/main/js/navigator/filters/choice-filters.js @@ -57,7 +57,7 @@ define([ onCheck: function(e) { var checkbox = jQuery(e.currentTarget), id = checkbox.data('id'), - checked = checkbox.children('.icon-checkbox-checked').length > 0; + checked = checkbox.find('.icon-checkbox-checked').length > 0; if (this.model.get('multiple')) { if (checkbox.closest('.opposite').length > 0) { @@ -315,11 +315,19 @@ define([ item.set('checked', false); }); + var unknownValues = []; + _.each(value, function(v) { var cModel = that.choices.findWhere({ id: v }); - cModel.set('checked', true); + if (cModel) { + cModel.set('checked', true); + } else { + unknownValues.push(v); + } }); + value = _.difference(value, unknownValues); + this.model.set({ value: value, enabled: true diff --git a/server/sonar-web/src/main/js/navigator/filters/metric-filters.js b/server/sonar-web/src/main/js/navigator/filters/metric-filters.js index 0bb84819e0b..3a81da9a0c0 100644 --- a/server/sonar-web/src/main/js/navigator/filters/metric-filters.js +++ b/server/sonar-web/src/main/js/navigator/filters/metric-filters.js @@ -14,16 +14,37 @@ define([ inputChanged: function() { - var value = { - metric: this.$('[name=metric]').val(), + var metric = this.$('[name=metric]').val(), + isDifferentialMetric = metric.indexOf('new_') === 0, + periodSelect = this.$('[name=period]'), + period = periodSelect.val(), + optionZero = periodSelect.children('[value="0"]'), + value = { + metric: metric, metricText: this.$('[name=metric] option:selected').text(), - period: this.$('[name=period]').val(), + period: period, periodText: this.$('[name=period] option:selected').text(), op: this.$('[name=op]').val(), opText: this.$('[name=op] option:selected').text(), val: this.$('[name=val]').val(), valText: this.$('[name=val]').originalVal() }; + + if (isDifferentialMetric) { + optionZero.remove(); + if (period === '0') { + period = '1'; + } + } else { + if (optionZero.length === 0) { + periodSelect.prepend(this.periodZeroOption); + } + } + periodSelect.select2('destroy').val(period).select2({ + width: '100%', + minimumResultsForSearch: 100 + }); + this.updateDataType(value); this.model.set('value', value); }, @@ -46,6 +67,9 @@ define([ onRender: function() { + var periodZeroLabel = this.$('[name=period]').children('[value="0"]').html(); + this.periodZeroOption = '<option value="0">' + periodZeroLabel + '</option>'; + var value = this.model.get('value') || {}; this.$('[name=metric]').val(value.metric).select2({ width: '100%', @@ -68,7 +92,11 @@ define([ onShow: function() { var select = this.$('[name=metric]'); - select.select2('open'); + if (this.model.get('value')['metric'] === '') { + select.select2('open'); + } else { + select.select2('focus'); + } } }); diff --git a/server/sonar-web/src/main/js/navigator/filters/range-filters.js b/server/sonar-web/src/main/js/navigator/filters/range-filters.js index 8457ee356d6..1627895022e 100644 --- a/server/sonar-web/src/main/js/navigator/filters/range-filters.js +++ b/server/sonar-web/src/main/js/navigator/filters/range-filters.js @@ -171,12 +171,14 @@ define([ render: function() { RangeFilterView.prototype.render.apply(this, arguments); this.detailsView.$('input') - .prop('readonly', true) .prop('placeholder', '1970-01-31') .datepicker({ dateFormat: 'yy-mm-dd', changeMonth: true, changeYear: true + }) + .on('change', function () { + jQuery(this).datepicker('setDate', jQuery(this).val()); }); }, diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js index 2553a3a3033..750da1da56a 100644 --- a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js @@ -32,8 +32,17 @@ casper.test.begin(testName('Readonly Tests'), function suite(test) { casper.waitForSelector('.navigator-filters', function checkDefaultFilters() { test.assertVisible('input[type="text"].query-filter-input'); test.assertElementCount('.navigator-filter', 15); - test.assertElementCount('.navigator-filter-optional', 12 /* Only query, qProfile and 'More' are visible by default */); + test.assertElementCount('.navigator-filter-optional', 12 /* Only query, qProfile and 'More' are visible by default */); test.assertVisible('button.navigator-filter-submit'); + + + casper.click('.navigator-filter-more-criteria'); + casper.waitUntilVisible('.navigator-filter-details.active', function checkTagsAreOrdered() { + casper.click('.navigator-filter-details.active label[data-property="tags"]'); + test.assertSelectorHasText('.navigator-filter[data-property="tags"] option:nth-child(1)', 'brain-overload'); + test.assertSelectorHasText('.navigator-filter[data-property="tags"] option:nth-child(11)', 'unused'); + casper.click('.navigator-filter.active>.navigator-filter-disable'); + }); }); @@ -260,3 +269,28 @@ casper.test.begin(testName('Activation Tests'), function suite(test) { test.done(); }); }); + + +casper.test.begin(testName('Tag Navigation Test'), function suite(test) { + + casper.start(lib.buildUrl('coding-rules#tags=polop,bug,pilip,unused,palap'), function() { + lib.clearRequestMocks(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/rules/app', 'app_admin.json'); + lib.mockRequestFromFile('/api/rules/tags', 'tags.json'); + lib.mockRequestFromFile('/api/rules/search', 'search_x1.json'); + lib.mockRequestFromFile('/api/rules/show', 'show_x1.json'); + }); + + + casper.waitWhileSelector("div#coding-rules-page-loader", function checkTagFilterRestored() { + casper.waitForSelector('.navigator-filters', function checkDefaultFilters() { + test.assertElementCount('.navigator-filter-disabled', 11 /* Tag is enabled */); + test.assertSelectorHasText('.navigator-filter[data-property="tags"] .navigator-filter-value', 'bug, unused'); + }); + }); + + casper.run(function() { + test.done(); + }); +}); diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-lines-filters-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-lines-filters-spec.js index d2470ccc8a9..2960f784d25 100644 --- a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-lines-filters-spec.js +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-lines-filters-spec.js @@ -38,3 +38,32 @@ casper.test.begin(testName('Lines Filters'), function (test) { test.done(); }); }); + + +casper.test.begin(testName('Do Not Show Ncloc Filter If No Data'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/resources', 'resources-without-ncloc-data.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + casper.click('.js-header-tab-basic'); + casper.waitForSelector('[data-metric="ncloc"]'); + }) + + .then(function () { + test.assertDoesntExist('.js-filter-ncloc'); + }) + + .run(function () { + test.done(); + }); +}); diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources-without-ncloc-data.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources-without-ncloc-data.json new file mode 100644 index 00000000000..dfbacdf7b28 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources-without-ncloc-data.json @@ -0,0 +1,150 @@ +[ + { + "id": 19983, + "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java", + "name": "Cache.java", + "scope": "FIL", + "qualifier": "FIL", + "date": "2014-07-21T23:18:51+0200", + "creationDate": "2013-04-17T04:06:45+0200", + "lname": "src/main/java/org/sonar/batch/index/Cache.java", + "lang": "java", + "msr": [ + { + "key": "lines", + "val": 519.0, + "frmt_val": "519" + }, + { + "key": "ncloc", + "val": 379.0, + "frmt_val": "379" + }, + { + "key": "classes", + "val": 6.0, + "frmt_val": "6" + }, + { + "key": "functions", + "val": 56.0, + "frmt_val": "56" + }, + { + "key": "accessors", + "val": 0.0, + "frmt_val": "0" + }, + { + "key": "statements", + "val": 174.0, + "frmt_val": "174" + }, + { + "key": "public_api", + "val": 33.0, + "frmt_val": "33" + }, + { + "key": "comment_lines", + "val": 23.0, + "frmt_val": "23" + }, + { + "key": "comment_lines_density", + "val": 5.7, + "frmt_val": "5.7%" + }, + { + "key": "public_documented_api_density", + "val": 36.4, + "frmt_val": "36.4%" + }, + { + "key": "public_undocumented_api", + "val": 21.0, + "frmt_val": "21" + }, + { + "key": "complexity", + "val": 116.0, + "frmt_val": "116" + }, + { + "key": "function_complexity", + "val": 2.1, + "frmt_val": "2.1" + }, + { + "key": "coverage", + "val": 74.3, + "frmt_val": "74.3%" + }, + { + "key": "lines_to_cover", + "val": 194.0, + "frmt_val": "194" + }, + { + "key": "uncovered_lines", + "val": 50.0, + "frmt_val": "50" + }, + { + "key": "line_coverage", + "val": 74.2, + "frmt_val": "74.2%" + }, + { + "key": "conditions_to_cover", + "val": 16.0, + "frmt_val": "16" + }, + { + "key": "uncovered_conditions", + "val": 4.0, + "frmt_val": "4" + }, + { + "key": "branch_coverage", + "val": 75.0, + "frmt_val": "75.0%" + }, + { + "key": "duplicated_lines", + "val": 30.0, + "frmt_val": "30" + }, + { + "key": "duplicated_blocks", + "val": 2.0, + "frmt_val": "2" + }, + { + "key": "duplicated_files", + "val": 1.0, + "frmt_val": "1" + }, + { + "key": "duplicated_lines_density", + "val": 5.8, + "frmt_val": "5.8%" + }, + { + "key": "major_violations", + "val": 1.0, + "frmt_val": "1" + }, + { + "key": "minor_violations", + "val": 1.0, + "frmt_val": "1" + }, + { + "key": "info_violations", + "val": 4.0, + "frmt_val": "4" + } + ] + } +] diff --git a/server/sonar-web/src/main/js/third-party/moment.js b/server/sonar-web/src/main/js/third-party/moment.js index 0018faebf9f..4b6676d8aa0 100644 --- a/server/sonar-web/src/main/js/third-party/moment.js +++ b/server/sonar-web/src/main/js/third-party/moment.js @@ -1,5 +1,5 @@ //! moment.js -//! version : 2.5.1 +//! version : 2.7.0 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com @@ -11,8 +11,10 @@ ************************************/ var moment, - VERSION = "2.5.1", - global = this, + VERSION = "2.7.0", + // the global-scope this is NOT the global object in Node.js + globalScope = typeof global !== 'undefined' ? global : this, + oldGlobalMoment, round = Math.round, i, @@ -34,6 +36,7 @@ _f : null, _l : null, _strict : null, + _tzm : null, _isUTC : null, _offset : null, // optional. Combine with _isUTC _pf : null, @@ -41,7 +44,7 @@ }, // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'), + hasModule = (typeof module !== 'undefined' && module.exports), // ASP.NET json date format regex aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, @@ -52,7 +55,7 @@ isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, // parsing token regexes @@ -91,7 +94,7 @@ // iso time formats and regexes isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], ['HH:mm', /(T| )\d\d:\d\d/], ['HH', /(T| )\d\d/] @@ -122,6 +125,7 @@ w : 'week', W : 'isoWeek', M : 'month', + Q : 'quarter', y : 'year', DDD : 'dayOfYear', e : 'weekday', @@ -141,6 +145,16 @@ // format function strings formatFunctions = {}, + // default relative time thresholds + relativeTimeThresholds = { + s: 45, //seconds to minutes + m: 45, //minutes to hours + h: 22, //hours to days + dd: 25, //days to month (month == 1) + dm: 45, //days to months (months > 1) + dy: 345 //days to year + }, + // tokens to ordinalize and pad ordinalizeTokens = 'DDD w W M D d'.split(' '), paddedTokens = 'M D H h m s w W'.split(' '), @@ -280,6 +294,16 @@ lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + // Pick the first defined of two or three arguments. dfl comes from + // default. + function dfl(a, b, c) { + switch (arguments.length) { + case 2: return a != null ? a : b; + case 3: return a != null ? a : b != null ? b : c; + default: throw new Error("Implement me"); + } + } + function defaultParsingFlags() { // We need to deep clone this object, and es5 standard is not very // helpful. @@ -297,6 +321,23 @@ }; } + function deprecate(msg, fn) { + var firstTime = true; + function printMsg() { + if (moment.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && console.warn) { + console.warn("Deprecation warning: " + msg); + } + } + return extend(function () { + if (firstTime) { + printMsg(); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + function padToken(func, count) { return function (a) { return leftZeroFill(func.call(this, a), count); @@ -337,6 +378,7 @@ function Duration(duration) { var normalizedInput = normalizeObjectUnits(duration), years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, months = normalizedInput.month || 0, weeks = normalizedInput.week || 0, days = normalizedInput.day || 0, @@ -358,6 +400,7 @@ // which months you are are talking about, so we have to store // it separately. this._months = +months + + quarters * 3 + years * 12; this._data = {}; @@ -420,35 +463,24 @@ } // helper function for _.addTime and _.subtractTime - function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { + function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { var milliseconds = duration._milliseconds, days = duration._days, - months = duration._months, - minutes, - hours; + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; if (milliseconds) { mom._d.setTime(+mom._d + milliseconds * isAdding); } - // store the minutes and hours so we can restore them - if (days || months) { - minutes = mom.minute(); - hours = mom.hour(); - } if (days) { - mom.date(mom.date() + days * isAdding); + rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); } if (months) { - mom.month(mom.month() + months * isAdding); + rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); } - if (milliseconds && !ignoreUpdateOffset) { + if (updateOffset) { moment.updateOffset(mom, days || months); } - // restore the minutes and hours after possibly changing dst - if (days || months) { - mom.minute(minutes); - mom.hour(hours); - } } // check if is an array @@ -956,6 +988,8 @@ function getParseRegexForToken(token, config) { var a, strict = config._strict; switch (token) { + case 'Q': + return parseTokenOneDigit; case 'DDDD': return parseTokenThreeDigits; case 'YYYY': @@ -1047,6 +1081,12 @@ var a, datePartArray = config._a; switch (token) { + // QUARTER + case 'Q': + if (input != null) { + datePartArray[MONTH] = (toInt(input) - 1) * 3; + } + break; // MONTH case 'M' : // fall through to MM case 'MM' : @@ -1086,7 +1126,7 @@ break; // YEAR case 'YY' : - datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + datePartArray[YEAR] = moment.parseTwoDigitYear(input); break; case 'YYYY' : case 'YYYYY' : @@ -1132,39 +1172,94 @@ config._useUTC = true; config._tzm = timezoneMinutesFromString(input); break; + // WEEKDAY - human + case 'dd': + case 'ddd': + case 'dddd': + a = getLangDefinition(config._l).weekdaysParse(input); + // if we didn't get a weekday name, mark the date as invalid + if (a != null) { + config._w = config._w || {}; + config._w['d'] = a; + } else { + config._pf.invalidWeekday = input; + } + break; + // WEEK, WEEK DAY - numeric case 'w': case 'ww': case 'W': case 'WW': case 'd': - case 'dd': - case 'ddd': - case 'dddd': case 'e': case 'E': token = token.substr(0, 1); /* falls through */ - case 'gg': case 'gggg': - case 'GG': case 'GGGG': case 'GGGGG': token = token.substr(0, 2); if (input) { config._w = config._w || {}; - config._w[token] = input; + config._w[token] = toInt(input); } break; + case 'gg': + case 'GG': + config._w = config._w || {}; + config._w[token] = moment.parseTwoDigitYear(input); } } + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, lang; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); + week = dfl(w.W, 1); + weekday = dfl(w.E, 1); + } else { + lang = getLangDefinition(config._l); + dow = lang._week.dow; + doy = lang._week.doy; + + weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); + week = dfl(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < dow) { + ++week; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + } else { + // default to begining of week + weekday = dow; + } + } + temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + // convert an array to a date. // the array should mirror the parameters below // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] function dateFromConfig(config) { - var i, date, input = [], currentDate, - yearToUse, fixYear, w, temp, lang, weekday, week; + var i, date, input = [], currentDate, yearToUse; if (config._d) { return; @@ -1174,39 +1269,12 @@ //compute day of the year from weeks and weekdays if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - fixYear = function (val) { - var int_val = parseInt(val, 10); - return val ? - (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) : - (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); - }; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); - } - else { - lang = getLangDefinition(config._l); - weekday = w.d != null ? parseWeekday(w.d, lang) : - (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); - - week = parseInt(w.w, 10) || 1; - - //if we're parsing 'd', then the low day numbers may be next week - if (w.d != null && weekday < lang._week.dow) { - week++; - } - - temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); - } - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; + dayOfYearFromWeekInfo(config); } //if the day of the year is set, figure out what it is if (config._dayOfYear) { - yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); if (config._dayOfYear > daysInYear(yearToUse)) { config._pf._overflowDayOfYear = true; @@ -1231,11 +1299,12 @@ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; } - // add the offsets to the time to be parsed so that we can have a clean array for checking isValid - input[HOUR] += toInt((config._tzm || 0) / 60); - input[MINUTE] += toInt((config._tzm || 0) % 60); - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + // Apply timezone offset from input. The actual zone can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm); + } } function dateFromObject(config) { @@ -1275,6 +1344,11 @@ // date from string and format string function makeDateFromStringAndFormat(config) { + if (config._f === moment.ISO_8601) { + parseISO(config); + return; + } + config._a = []; config._pf.empty = true; @@ -1387,7 +1461,7 @@ } // date from iso format - function makeDateFromString(config) { + function parseISO(config) { var i, l, string = config._i, match = isoRegex.exec(string); @@ -1411,9 +1485,17 @@ config._f += "Z"; } makeDateFromStringAndFormat(config); + } else { + config._isValid = false; } - else { - config._d = new Date(string); + } + + // date from iso format or fallback + function makeDateFromString(config) { + parseISO(config); + if (config._isValid === false) { + delete config._isValid; + moment.createFromInputFallback(config); } } @@ -1434,8 +1516,11 @@ config._d = new Date(+input); } else if (typeof(input) === 'object') { dateFromObject(config); - } else { + } else if (typeof(input) === 'number') { + // from milliseconds config._d = new Date(input); + } else { + moment.createFromInputFallback(config); } } @@ -1490,15 +1575,15 @@ hours = round(minutes / 60), days = round(hours / 24), years = round(days / 365), - args = seconds < 45 && ['s', seconds] || + args = seconds < relativeTimeThresholds.s && ['s', seconds] || minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || + minutes < relativeTimeThresholds.m && ['mm', minutes] || hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || + hours < relativeTimeThresholds.h && ['hh', hours] || days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || + days <= relativeTimeThresholds.dd && ['dd', days] || + days <= relativeTimeThresholds.dm && ['M'] || + days < relativeTimeThresholds.dy && ['MM', round(days / 30)] || years === 1 && ['y'] || ['yy', years]; args[2] = withoutSuffix; args[3] = milliseconds > 0; @@ -1544,6 +1629,7 @@ function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + d = d === 0 ? 7 : d; weekday = weekday != null ? weekday : firstDayOfWeek; daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; @@ -1562,7 +1648,7 @@ var input = config._i, format = config._f; - if (input === null) { + if (input === null || (format === undefined && input === '')) { return moment.invalid({nullInput: true}); } @@ -1608,6 +1694,51 @@ return makeMoment(c); }; + moment.suppressDeprecationWarnings = false; + + moment.createFromInputFallback = deprecate( + "moment construction falls back to js Date. This is " + + "discouraged and will be removed in upcoming major " + + "release. Please refer to " + + "https://github.com/moment/moment/issues/1407 for more info.", + function (config) { + config._d = new Date(config._i); + }); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return moment(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + moment.min = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + }; + + moment.max = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + }; + // creating with utc moment.utc = function (input, format, lang, strict) { var c; @@ -1704,10 +1835,26 @@ // default format moment.defaultFormat = isoFormat; + // constant that refers to the ISO standard + moment.ISO_8601 = function () {}; + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + moment.momentProperties = momentProperties; + // This function will be called whenever a moment is mutated. // It is intended to keep the offset in sync with the timezone. moment.updateOffset = function () {}; + // This function allows you to set a threshold for relative time strings + moment.relativeTimeThreshold = function(threshold, limit) { + if (relativeTimeThresholds[threshold] === undefined) { + return false; + } + relativeTimeThresholds[threshold] = limit; + return true; + }; + // This function will load languages and then set the global language. If // no arguments are passed in, it will simply return the current global // language key. @@ -1771,6 +1918,10 @@ return moment.apply(null, arguments).parseZone(); }; + moment.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + /************************************ Moment Prototype ************************************/ @@ -1859,7 +2010,9 @@ add : function (input, val) { var dur; // switch args to support add('s', 1) and add(1, 's') - if (typeof input === 'string') { + if (typeof input === 'string' && typeof val === 'string') { + dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input); + } else if (typeof input === 'string') { dur = moment.duration(+val, input); } else { dur = moment.duration(input, val); @@ -1871,7 +2024,9 @@ subtract : function (input, val) { var dur; // switch args to support subtract('s', 1) and subtract(1, 's') - if (typeof input === 'string') { + if (typeof input === 'string' && typeof val === 'string') { + dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input); + } else if (typeof input === 'string') { dur = moment.duration(+val, input); } else { dur = moment.duration(input, val); @@ -1922,10 +2077,11 @@ return this.from(moment(), withoutSuffix); }, - calendar : function () { + calendar : function (time) { // We want to compare the start of today, vs this. // Getting start-of-today depends on whether we're zone'd or not. - var sod = makeAs(moment(), this).startOf('day'), + var now = time || moment(), + sod = makeAs(now, this).startOf('day'), diff = this.diff(sod, 'days', true), format = diff < -6 ? 'sameElse' : diff < -1 ? 'lastWeek' : @@ -1955,27 +2111,7 @@ } }, - month : function (input) { - var utc = this._isUTC ? 'UTC' : '', - dayOfMonth; - - if (input != null) { - if (typeof input === 'string') { - input = this.lang().monthsParse(input); - if (typeof input !== 'number') { - return this; - } - } - - dayOfMonth = Math.min(this.date(), - daysInMonth(this.year(), input)); - this._d['set' + utc + 'Month'](input, dayOfMonth); - moment.updateOffset(this, true); - return this; - } else { - return this._d['get' + utc + 'Month'](); - } - }, + month : makeAccessor('Month', true), startOf: function (units) { units = normalizeUnits(units); @@ -1985,6 +2121,7 @@ case 'year': this.month(0); /* falls through */ + case 'quarter': case 'month': this.date(1); /* falls through */ @@ -2011,6 +2148,11 @@ this.isoWeekday(1); } + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + return this; }, @@ -2034,18 +2176,33 @@ return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); }, - min: function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - }, - - max: function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - }, - - zone : function (input, adjust) { - adjust = (adjust == null ? true : false); + min: deprecate( + "moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548", + function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + } + ), + + max: deprecate( + "moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548", + function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + } + ), + + // keepTime = true means only change the timezone, without affecting + // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200 + // It is possible that 5:31:26 doesn't exist int zone +0200, so we + // adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + zone : function (input, keepTime) { var offset = this._offset || 0; if (input != null) { if (typeof input === "string") { @@ -2056,8 +2213,15 @@ } this._offset = input; this._isUTC = true; - if (offset !== input && adjust) { - addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); + if (offset !== input) { + if (!keepTime || this._changeInProgress) { + addOrSubtractDurationFromMoment(this, + moment.duration(offset - input, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + moment.updateOffset(this, true); + this._changeInProgress = null; + } } } else { return this._isUTC ? offset : this._d.getTimezoneOffset(); @@ -2102,8 +2266,8 @@ return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); }, - quarter : function () { - return Math.ceil((this.month() + 1.0) / 3.0); + quarter : function (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); }, weekYear : function (input) { @@ -2173,41 +2337,68 @@ } }); - // helper for adding shortcuts - function makeGetterAndSetter(name, key) { - // ignoreOffsetTransitions provides a hint to updateOffset to not - // change hours/minutes when crossing a tz boundary. This is frequently - // desirable when modifying part of an existing moment object directly. - var defaultIgnoreOffsetTransitions = key === 'date' || key === 'month' || key === 'year'; - moment.fn[name] = moment.fn[name + 's'] = function (input, ignoreOffsetTransitions) { - var utc = this._isUTC ? 'UTC' : ''; - if (ignoreOffsetTransitions == null) { - ignoreOffsetTransitions = defaultIgnoreOffsetTransitions; + function rawMonthSetter(mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.lang().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; } - if (input != null) { - this._d['set' + utc + key](input); - moment.updateOffset(this, ignoreOffsetTransitions); + } + + dayOfMonth = Math.min(mom.date(), + daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function rawGetter(mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } + + function rawSetter(mom, unit, value) { + if (unit === 'Month') { + return rawMonthSetter(mom, value); + } else { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + + function makeAccessor(unit, keepTime) { + return function (value) { + if (value != null) { + rawSetter(this, unit, value); + moment.updateOffset(this, keepTime); return this; } else { - return this._d['get' + utc + key](); + return rawGetter(this, unit); } }; } - // loop through and add shortcuts (Date, Hours, Minutes, Seconds, Milliseconds) - // Month has a custom getter/setter. - for (i = 0; i < proxyGettersAndSetters.length; i ++) { - makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); - } - - // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') - makeGetterAndSetter('year', 'FullYear'); + moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); + moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); + moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); + // moment.fn.month is defined separately + moment.fn.date = makeAccessor('Date', true); + moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true)); + moment.fn.year = makeAccessor('FullYear', true); + moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true)); // add plural methods moment.fn.days = moment.fn.day; moment.fn.months = moment.fn.month; moment.fn.weeks = moment.fn.week; moment.fn.isoWeeks = moment.fn.isoWeek; + moment.fn.quarters = moment.fn.quarter; // add aliased format methods moment.fn.toJSON = moment.fn.toISOString; @@ -2377,51 +2568,5953 @@ } }); - /* EMBED_LANGUAGES */ + // moment.js language configuration +// language : Moroccan Arabic (ar-ma) +// author : ElFadili Yassine : https://github.com/ElFadiliY +// author : Abdel Said : https://github.com/abdelsaid + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('ar-ma', { + months : "يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"), + monthsShort : "يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"), + weekdays : "الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"), + weekdaysShort : "احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"), + weekdaysMin : "ح_ن_ث_ر_خ_ج_س".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: "[اليوم على الساعة] LT", + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : "في %s", + past : "منذ %s", + s : "ثوان", + m : "دقيقة", + mm : "%d دقائق", + h : "ساعة", + hh : "%d ساعات", + d : "يوم", + dd : "%d أيام", + M : "شهر", + MM : "%d أشهر", + y : "سنة", + yy : "%d سنوات" + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Arabic Saudi Arabia (ar-sa) +// author : Suhail Alkowaileet : https://github.com/xsoh + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' + }, numberMap = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' + }; + + return moment.lang('ar-sa', { + months : "يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"), + monthsShort : "يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"), + weekdays : "الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"), + weekdaysShort : "أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"), + weekdaysMin : "ح_ن_ث_ر_خ_ج_س".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return "ص"; + } else { + return "م"; + } + }, + calendar : { + sameDay: "[اليوم على الساعة] LT", + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : "في %s", + past : "منذ %s", + s : "ثوان", + m : "دقيقة", + mm : "%d دقائق", + h : "ساعة", + hh : "%d ساعات", + d : "يوم", + dd : "%d أيام", + M : "شهر", + MM : "%d أشهر", + y : "سنة", + yy : "%d سنوات" + }, + preparse: function (string) { + return string.replace(/[۰-۹]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Arabic (ar) +// author : Abdel Said : https://github.com/abdelsaid +// changes in months, weekdays : Ahmed Elkhatib + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' + }, numberMap = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' + }; + + return moment.lang('ar', { + months : "يناير/ كانون الثاني_فبراير/ شباط_مارس/ آذار_أبريل/ نيسان_مايو/ أيار_يونيو/ حزيران_يوليو/ تموز_أغسطس/ آب_سبتمبر/ أيلول_أكتوبر/ تشرين الأول_نوفمبر/ تشرين الثاني_ديسمبر/ كانون الأول".split("_"), + monthsShort : "يناير/ كانون الثاني_فبراير/ شباط_مارس/ آذار_أبريل/ نيسان_مايو/ أيار_يونيو/ حزيران_يوليو/ تموز_أغسطس/ آب_سبتمبر/ أيلول_أكتوبر/ تشرين الأول_نوفمبر/ تشرين الثاني_ديسمبر/ كانون الأول".split("_"), + weekdays : "الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"), + weekdaysShort : "أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"), + weekdaysMin : "ح_ن_ث_ر_خ_ج_س".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return "ص"; + } else { + return "م"; + } + }, + calendar : { + sameDay: "[اليوم على الساعة] LT", + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : "في %s", + past : "منذ %s", + s : "ثوان", + m : "دقيقة", + mm : "%d دقائق", + h : "ساعة", + hh : "%d ساعات", + d : "يوم", + dd : "%d أيام", + M : "شهر", + MM : "%d أشهر", + y : "سنة", + yy : "%d سنوات" + }, + preparse: function (string) { + return string.replace(/[۰-۹]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : azerbaijani (az) +// author : topchiyev : https://github.com/topchiyev + +(function (factory) { + factory(moment); +}(function (moment) { + + var suffixes = { + 1: "-inci", + 5: "-inci", + 8: "-inci", + 70: "-inci", + 80: "-inci", + + 2: "-nci", + 7: "-nci", + 20: "-nci", + 50: "-nci", + + 3: "-üncü", + 4: "-üncü", + 100: "-üncü", + + 6: "-ncı", + + 9: "-uncu", + 10: "-uncu", + 30: "-uncu", + + 60: "-ıncı", + 90: "-ıncı" + }; + return moment.lang('az', { + months : "yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"), + monthsShort : "yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"), + weekdays : "Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə".split("_"), + weekdaysShort : "Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən".split("_"), + weekdaysMin : "Bz_BE_ÇA_Çə_CA_Cü_Şə".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[bugün saat] LT', + nextDay : '[sabah saat] LT', + nextWeek : '[gələn həftə] dddd [saat] LT', + lastDay : '[dünən] LT', + lastWeek : '[keçən həftə] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s sonra", + past : "%s əvvəl", + s : "birneçə saniyyə", + m : "bir dəqiqə", + mm : "%d dəqiqə", + h : "bir saat", + hh : "%d saat", + d : "bir gün", + dd : "%d gün", + M : "bir ay", + MM : "%d ay", + y : "bir il", + yy : "%d il" + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return "gecə"; + } else if (hour < 12) { + return "səhər"; + } else if (hour < 17) { + return "gündüz"; + } else { + return "axşam"; + } + }, + ordinal : function (number) { + if (number === 0) { // special case for zero + return number + "-ıncı"; + } + var a = number % 10, + b = number % 100 - a, + c = number >= 100 ? 100 : null; + + return number + (suffixes[a] || suffixes[b] || suffixes[c]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : bulgarian (bg) +// author : Krasen Borisov : https://github.com/kraz + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('bg', { + months : "януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември".split("_"), + monthsShort : "янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек".split("_"), + weekdays : "неделя_понеделник_вторник_сряда_четвъртък_петък_събота".split("_"), + weekdaysShort : "нед_пон_вто_сря_чет_пет_съб".split("_"), + weekdaysMin : "нд_пн_вт_ср_чт_пт_сб".split("_"), + longDateFormat : { + LT : "H:mm", + L : "D.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[Днес в] LT', + nextDay : '[Утре в] LT', + nextWeek : 'dddd [в] LT', + lastDay : '[Вчера в] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[В изминалата] dddd [в] LT'; + case 1: + case 2: + case 4: + case 5: + return '[В изминалия] dddd [в] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : "след %s", + past : "преди %s", + s : "няколко секунди", + m : "минута", + mm : "%d минути", + h : "час", + hh : "%d часа", + d : "ден", + dd : "%d дни", + M : "месец", + MM : "%d месеца", + y : "година", + yy : "%d години" + }, + ordinal : function (number) { + var lastDigit = number % 10, + last2Digits = number % 100; + if (number === 0) { + return number + '-ев'; + } else if (last2Digits === 0) { + return number + '-ен'; + } else if (last2Digits > 10 && last2Digits < 20) { + return number + '-ти'; + } else if (lastDigit === 1) { + return number + '-ви'; + } else if (lastDigit === 2) { + return number + '-ри'; + } else if (lastDigit === 7 || lastDigit === 8) { + return number + '-ми'; + } else { + return number + '-ти'; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Bengali (bn) +// author : Kaushik Gandhi : https://github.com/kaushikgandhi + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '১', + '2': '২', + '3': '৩', + '4': '৪', + '5': '৫', + '6': '৬', + '7': '৭', + '8': '৮', + '9': '৯', + '0': '০' + }, + numberMap = { + '১': '1', + '২': '2', + '৩': '3', + '৪': '4', + '৫': '5', + '৬': '6', + '৭': '7', + '৮': '8', + '৯': '9', + '০': '0' + }; + + return moment.lang('bn', { + months : 'জানুয়ারী_ফেবুয়ারী_মার্চ_এপ্রিল_মে_জুন_জুলাই_অগাস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split("_"), + monthsShort : 'জানু_ফেব_মার্চ_এপর_মে_জুন_জুল_অগ_সেপ্ট_অক্টো_নভ_ডিসেম্'.split("_"), + weekdays : 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পত্তিবার_শুক্রুবার_শনিবার'.split("_"), + weekdaysShort : 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পত্তি_শুক্রু_শনি'.split("_"), + weekdaysMin : 'রব_সম_মঙ্গ_বু_ব্রিহ_শু_শনি'.split("_"), + longDateFormat : { + LT : "A h:mm সময়", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY, LT", + LLLL : "dddd, D MMMM YYYY, LT" + }, + calendar : { + sameDay : '[আজ] LT', + nextDay : '[আগামীকাল] LT', + nextWeek : 'dddd, LT', + lastDay : '[গতকাল] LT', + lastWeek : '[গত] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s পরে", + past : "%s আগে", + s : "কএক সেকেন্ড", + m : "এক মিনিট", + mm : "%d মিনিট", + h : "এক ঘন্টা", + hh : "%d ঘন্টা", + d : "এক দিন", + dd : "%d দিন", + M : "এক মাস", + MM : "%d মাস", + y : "এক বছর", + yy : "%d বছর" + }, + preparse: function (string) { + return string.replace(/[১২৩৪৫৬৭৮৯০]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + //Bengali is a vast language its spoken + //in different forms in various parts of the world. + //I have just generalized with most common one used + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return "রাত"; + } else if (hour < 10) { + return "শকাল"; + } else if (hour < 17) { + return "দুপুর"; + } else if (hour < 20) { + return "বিকেল"; + } else { + return "রাত"; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : breton (br) +// author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou + +(function (factory) { + factory(moment); +}(function (moment) { + function relativeTimeWithMutation(number, withoutSuffix, key) { + var format = { + 'mm': "munutenn", + 'MM': "miz", + 'dd': "devezh" + }; + return number + ' ' + mutation(format[key], number); + } + + function specialMutationForYears(number) { + switch (lastNumber(number)) { + case 1: + case 3: + case 4: + case 5: + case 9: + return number + ' bloaz'; + default: + return number + ' vloaz'; + } + } + + function lastNumber(number) { + if (number > 9) { + return lastNumber(number % 10); + } + return number; + } + + function mutation(text, number) { + if (number === 2) { + return softMutation(text); + } + return text; + } + + function softMutation(text) { + var mutationTable = { + 'm': 'v', + 'b': 'v', + 'd': 'z' + }; + if (mutationTable[text.charAt(0)] === undefined) { + return text; + } + return mutationTable[text.charAt(0)] + text.substring(1); + } + + return moment.lang('br', { + months : "Genver_C'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu".split("_"), + monthsShort : "Gen_C'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker".split("_"), + weekdays : "Sul_Lun_Meurzh_Merc'her_Yaou_Gwener_Sadorn".split("_"), + weekdaysShort : "Sul_Lun_Meu_Mer_Yao_Gwe_Sad".split("_"), + weekdaysMin : "Su_Lu_Me_Mer_Ya_Gw_Sa".split("_"), + longDateFormat : { + LT : "h[e]mm A", + L : "DD/MM/YYYY", + LL : "D [a viz] MMMM YYYY", + LLL : "D [a viz] MMMM YYYY LT", + LLLL : "dddd, D [a viz] MMMM YYYY LT" + }, + calendar : { + sameDay : '[Hiziv da] LT', + nextDay : '[Warc\'hoazh da] LT', + nextWeek : 'dddd [da] LT', + lastDay : '[Dec\'h da] LT', + lastWeek : 'dddd [paset da] LT', + sameElse : 'L' + }, + relativeTime : { + future : "a-benn %s", + past : "%s 'zo", + s : "un nebeud segondennoù", + m : "ur vunutenn", + mm : relativeTimeWithMutation, + h : "un eur", + hh : "%d eur", + d : "un devezh", + dd : relativeTimeWithMutation, + M : "ur miz", + MM : relativeTimeWithMutation, + y : "ur bloaz", + yy : specialMutationForYears + }, + ordinal : function (number) { + var output = (number === 1) ? 'añ' : 'vet'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : bosnian (bs) +// author : Nedim Cholich : https://github.com/frontyard +// based on (hr) translation by Bojan Marković + +(function (factory) { + factory(moment); +}(function (moment) { + + function translate(number, withoutSuffix, key) { + var result = number + " "; + switch (key) { + case 'm': + return withoutSuffix ? 'jedna minuta' : 'jedne minute'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minuta'; + } + return result; + case 'h': + return withoutSuffix ? 'jedan sat' : 'jednog sata'; + case 'hh': + if (number === 1) { + result += 'sat'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sata'; + } else { + result += 'sati'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dana'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mjesec'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'mjeseca'; + } else { + result += 'mjeseci'; + } + return result; + case 'yy': + if (number === 1) { + result += 'godina'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'godine'; + } else { + result += 'godina'; + } + return result; + } + } + + return moment.lang('bs', { + months : "januar_februar_mart_april_maj_juni_juli_avgust_septembar_oktobar_novembar_decembar".split("_"), + monthsShort : "jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"), + weekdays : "nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"), + weekdaysShort : "ned._pon._uto._sri._čet._pet._sub.".split("_"), + weekdaysMin : "ne_po_ut_sr_če_pe_su".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD. MM. YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd, D. MMMM YYYY LT" + }, + calendar : { + sameDay : '[danas u] LT', + nextDay : '[sutra u] LT', + + nextWeek : function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[jučer u] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prošli] dddd [u] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : "za %s", + past : "prije %s", + s : "par sekundi", + m : translate, + mm : translate, + h : translate, + hh : translate, + d : "dan", + dd : translate, + M : "mjesec", + MM : translate, + y : "godinu", + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : catalan (ca) +// author : Juan G. Hurtado : https://github.com/juanghurtado + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('ca', { + months : "gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre".split("_"), + monthsShort : "gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.".split("_"), + weekdays : "diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte".split("_"), + weekdaysShort : "dg._dl._dt._dc._dj._dv._ds.".split("_"), + weekdaysMin : "Dg_Dl_Dt_Dc_Dj_Dv_Ds".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay : function () { + return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + nextDay : function () { + return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + lastDay : function () { + return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : "en %s", + past : "fa %s", + s : "uns segons", + m : "un minut", + mm : "%d minuts", + h : "una hora", + hh : "%d hores", + d : "un dia", + dd : "%d dies", + M : "un mes", + MM : "%d mesos", + y : "un any", + yy : "%d anys" + }, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : czech (cs) +// author : petrbela : https://github.com/petrbela + +(function (factory) { + factory(moment); +}(function (moment) { + var months = "leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"), + monthsShort = "led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_"); + + function plural(n) { + return (n > 1) && (n < 5) && (~~(n / 10) !== 1); + } + + function translate(number, withoutSuffix, key, isFuture) { + var result = number + " "; + switch (key) { + case 's': // a few seconds / in a few seconds / a few seconds ago + return (withoutSuffix || isFuture) ? 'pár sekund' : 'pár sekundami'; + case 'm': // a minute / in a minute / a minute ago + return withoutSuffix ? 'minuta' : (isFuture ? 'minutu' : 'minutou'); + case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'minuty' : 'minut'); + } else { + return result + 'minutami'; + } + break; + case 'h': // an hour / in an hour / an hour ago + return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); + case 'hh': // 9 hours / in 9 hours / 9 hours ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'hodiny' : 'hodin'); + } else { + return result + 'hodinami'; + } + break; + case 'd': // a day / in a day / a day ago + return (withoutSuffix || isFuture) ? 'den' : 'dnem'; + case 'dd': // 9 days / in 9 days / 9 days ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'dny' : 'dní'); + } else { + return result + 'dny'; + } + break; + case 'M': // a month / in a month / a month ago + return (withoutSuffix || isFuture) ? 'měsíc' : 'měsícem'; + case 'MM': // 9 months / in 9 months / 9 months ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'měsíce' : 'měsíců'); + } else { + return result + 'měsíci'; + } + break; + case 'y': // a year / in a year / a year ago + return (withoutSuffix || isFuture) ? 'rok' : 'rokem'; + case 'yy': // 9 years / in 9 years / 9 years ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'roky' : 'let'); + } else { + return result + 'lety'; + } + break; + } + } + + return moment.lang('cs', { + months : months, + monthsShort : monthsShort, + monthsParse : (function (months, monthsShort) { + var i, _monthsParse = []; + for (i = 0; i < 12; i++) { + // use custom parser to solve problem with July (červenec) + _monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i'); + } + return _monthsParse; + }(months, monthsShort)), + weekdays : "neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"), + weekdaysShort : "ne_po_út_st_čt_pá_so".split("_"), + weekdaysMin : "ne_po_út_st_čt_pá_so".split("_"), + longDateFormat : { + LT: "H.mm", + L : "DD. MM. YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd D. MMMM YYYY LT" + }, + calendar : { + sameDay: "[dnes v] LT", + nextDay: '[zítra v] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[v neděli v] LT'; + case 1: + case 2: + return '[v] dddd [v] LT'; + case 3: + return '[ve středu v] LT'; + case 4: + return '[ve čtvrtek v] LT'; + case 5: + return '[v pátek v] LT'; + case 6: + return '[v sobotu v] LT'; + } + }, + lastDay: '[včera v] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[minulou neděli v] LT'; + case 1: + case 2: + return '[minulé] dddd [v] LT'; + case 3: + return '[minulou středu v] LT'; + case 4: + case 5: + return '[minulý] dddd [v] LT'; + case 6: + return '[minulou sobotu v] LT'; + } + }, + sameElse: "L" + }, + relativeTime : { + future : "za %s", + past : "před %s", + s : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : chuvash (cv) +// author : Anatoly Mironov : https://github.com/mirontoli + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('cv', { + months : "кăрлач_нарăс_пуш_ака_май_çĕртме_утă_çурла_авăн_юпа_чӳк_раштав".split("_"), + monthsShort : "кăр_нар_пуш_ака_май_çĕр_утă_çур_ав_юпа_чӳк_раш".split("_"), + weekdays : "вырсарникун_тунтикун_ытларикун_юнкун_кĕçнерникун_эрнекун_шăматкун".split("_"), + weekdaysShort : "выр_тун_ытл_юн_кĕç_эрн_шăм".split("_"), + weekdaysMin : "вр_тн_ыт_юн_кç_эр_шм".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD-MM-YYYY", + LL : "YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ]", + LLL : "YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ], LT", + LLLL : "dddd, YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ], LT" + }, + calendar : { + sameDay: '[Паян] LT [сехетре]', + nextDay: '[Ыран] LT [сехетре]', + lastDay: '[Ĕнер] LT [сехетре]', + nextWeek: '[Çитес] dddd LT [сехетре]', + lastWeek: '[Иртнĕ] dddd LT [сехетре]', + sameElse: 'L' + }, + relativeTime : { + future : function (output) { + var affix = /сехет$/i.exec(output) ? "рен" : /çул$/i.exec(output) ? "тан" : "ран"; + return output + affix; + }, + past : "%s каялла", + s : "пĕр-ик çеккунт", + m : "пĕр минут", + mm : "%d минут", + h : "пĕр сехет", + hh : "%d сехет", + d : "пĕр кун", + dd : "%d кун", + M : "пĕр уйăх", + MM : "%d уйăх", + y : "пĕр çул", + yy : "%d çул" + }, + ordinal : '%d-мĕш', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Welsh (cy) +// author : Robert Allen + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang("cy", { + months: "Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr".split("_"), + monthsShort: "Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag".split("_"), + weekdays: "Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn".split("_"), + weekdaysShort: "Sul_Llun_Maw_Mer_Iau_Gwe_Sad".split("_"), + weekdaysMin: "Su_Ll_Ma_Me_Ia_Gw_Sa".split("_"), + // time formats are the same as en-gb + longDateFormat: { + LT: "HH:mm", + L: "DD/MM/YYYY", + LL: "D MMMM YYYY", + LLL: "D MMMM YYYY LT", + LLLL: "dddd, D MMMM YYYY LT" + }, + calendar: { + sameDay: '[Heddiw am] LT', + nextDay: '[Yfory am] LT', + nextWeek: 'dddd [am] LT', + lastDay: '[Ddoe am] LT', + lastWeek: 'dddd [diwethaf am] LT', + sameElse: 'L' + }, + relativeTime: { + future: "mewn %s", + past: "%s yn ôl", + s: "ychydig eiliadau", + m: "munud", + mm: "%d munud", + h: "awr", + hh: "%d awr", + d: "diwrnod", + dd: "%d diwrnod", + M: "mis", + MM: "%d mis", + y: "blwyddyn", + yy: "%d flynedd" + }, + // traditional ordinal numbers above 31 are not commonly used in colloquial Welsh + ordinal: function (number) { + var b = number, + output = '', + lookup = [ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed' // 11eg to 20fed + ]; + + if (b > 20) { + if (b === 40 || b === 50 || b === 60 || b === 80 || b === 100) { + output = 'fed'; // not 30ain, 70ain or 90ain + } else { + output = 'ain'; + } + } else if (b > 0) { + output = lookup[b]; + } + + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : danish (da) +// author : Ulrik Nielsen : https://github.com/mrbase + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('da', { + months : "januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"), + monthsShort : "jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"), + weekdays : "søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"), + weekdaysShort : "søn_man_tir_ons_tor_fre_lør".split("_"), + weekdaysMin : "sø_ma_ti_on_to_fr_lø".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd [d.] D. MMMM YYYY LT" + }, + calendar : { + sameDay : '[I dag kl.] LT', + nextDay : '[I morgen kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[I går kl.] LT', + lastWeek : '[sidste] dddd [kl] LT', + sameElse : 'L' + }, + relativeTime : { + future : "om %s", + past : "%s siden", + s : "få sekunder", + m : "et minut", + mm : "%d minutter", + h : "en time", + hh : "%d timer", + d : "en dag", + dd : "%d dage", + M : "en måned", + MM : "%d måneder", + y : "et år", + yy : "%d år" + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : austrian german (de-at) +// author : lluchs : https://github.com/lluchs +// author: Menelion Elensúle: https://github.com/Oire +// author : Martin Groller : https://github.com/MadMG + +(function (factory) { + factory(moment); +}(function (moment) { + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + return moment.lang('de-at', { + months : "Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"), + monthsShort : "Jän._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"), + weekdays : "Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"), + weekdaysShort : "So._Mo._Di._Mi._Do._Fr._Sa.".split("_"), + weekdaysMin : "So_Mo_Di_Mi_Do_Fr_Sa".split("_"), + longDateFormat : { + LT: "HH:mm [Uhr]", + L : "DD.MM.YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd, D. MMMM YYYY LT" + }, + calendar : { + sameDay: "[Heute um] LT", + sameElse: "L", + nextDay: '[Morgen um] LT', + nextWeek: 'dddd [um] LT', + lastDay: '[Gestern um] LT', + lastWeek: '[letzten] dddd [um] LT' + }, + relativeTime : { + future : "in %s", + past : "vor %s", + s : "ein paar Sekunden", + m : processRelativeTime, + mm : "%d Minuten", + h : processRelativeTime, + hh : "%d Stunden", + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : german (de) +// author : lluchs : https://github.com/lluchs +// author: Menelion Elensúle: https://github.com/Oire + +(function (factory) { + factory(moment); +}(function (moment) { + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + return moment.lang('de', { + months : "Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"), + monthsShort : "Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"), + weekdays : "Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"), + weekdaysShort : "So._Mo._Di._Mi._Do._Fr._Sa.".split("_"), + weekdaysMin : "So_Mo_Di_Mi_Do_Fr_Sa".split("_"), + longDateFormat : { + LT: "HH:mm [Uhr]", + L : "DD.MM.YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd, D. MMMM YYYY LT" + }, + calendar : { + sameDay: "[Heute um] LT", + sameElse: "L", + nextDay: '[Morgen um] LT', + nextWeek: 'dddd [um] LT', + lastDay: '[Gestern um] LT', + lastWeek: '[letzten] dddd [um] LT' + }, + relativeTime : { + future : "in %s", + past : "vor %s", + s : "ein paar Sekunden", + m : processRelativeTime, + mm : "%d Minuten", + h : processRelativeTime, + hh : "%d Stunden", + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : modern greek (el) +// author : Aggelos Karalias : https://github.com/mehiel + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('el', { + monthsNominativeEl : "Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"), + monthsGenitiveEl : "Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"), + months : function (momentToFormat, format) { + if (/D/.test(format.substring(0, format.indexOf("MMMM")))) { // if there is a day number before 'MMMM' + return this._monthsGenitiveEl[momentToFormat.month()]; + } else { + return this._monthsNominativeEl[momentToFormat.month()]; + } + }, + monthsShort : "Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"), + weekdays : "Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"), + weekdaysShort : "Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"), + weekdaysMin : "Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"), + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'μμ' : 'ΜΜ'; + } else { + return isLower ? 'πμ' : 'ΠΜ'; + } + }, + longDateFormat : { + LT : "h:mm A", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendarEl : { + sameDay : '[Σήμερα {}] LT', + nextDay : '[Αύριο {}] LT', + nextWeek : 'dddd [{}] LT', + lastDay : '[Χθες {}] LT', + lastWeek : function() { + switch (this.day()) { + case 6: + return '[το προηγούμενο] dddd [{}] LT'; + default: + return '[την προηγούμενη] dddd [{}] LT'; + } + }, + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendarEl[key], + hours = mom && mom.hours(); + + if (typeof output === 'function') { + output = output.apply(mom); + } + + return output.replace("{}", (hours % 12 === 1 ? "στη" : "στις")); + }, + relativeTime : { + future : "σε %s", + past : "%s πριν", + s : "δευτερόλεπτα", + m : "ένα λεπτό", + mm : "%d λεπτά", + h : "μία ώρα", + hh : "%d ώρες", + d : "μία μέρα", + dd : "%d μέρες", + M : "ένας μήνας", + MM : "%d μήνες", + y : "ένας χρόνος", + yy : "%d χρόνια" + }, + ordinal : function (number) { + return number + 'η'; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : australian english (en-au) + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('en-au', { + months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + longDateFormat : { + LT : "h:mm A", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : canadian english (en-ca) +// author : Jonathan Abourbih : https://github.com/jonbca + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('en-ca', { + months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + longDateFormat : { + LT : "h:mm A", + L : "YYYY-MM-DD", + LL : "D MMMM, YYYY", + LLL : "D MMMM, YYYY LT", + LLLL : "dddd, D MMMM, YYYY LT" + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); +})); +// moment.js language configuration +// language : great britain english (en-gb) +// author : Chris Gedrim : https://github.com/chrisgedrim + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('en-gb', { + months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : esperanto (eo) +// author : Colin Dean : https://github.com/colindean +// komento: Mi estas malcerta se mi korekte traktis akuzativojn en tiu traduko. +// Se ne, bonvolu korekti kaj avizi min por ke mi povas lerni! + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('eo', { + months : "januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro".split("_"), + monthsShort : "jan_feb_mar_apr_maj_jun_jul_aŭg_sep_okt_nov_dec".split("_"), + weekdays : "Dimanĉo_Lundo_Mardo_Merkredo_Ĵaŭdo_Vendredo_Sabato".split("_"), + weekdaysShort : "Dim_Lun_Mard_Merk_Ĵaŭ_Ven_Sab".split("_"), + weekdaysMin : "Di_Lu_Ma_Me_Ĵa_Ve_Sa".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "YYYY-MM-DD", + LL : "D[-an de] MMMM, YYYY", + LLL : "D[-an de] MMMM, YYYY LT", + LLLL : "dddd, [la] D[-an de] MMMM, YYYY LT" + }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'p.t.m.' : 'P.T.M.'; + } else { + return isLower ? 'a.t.m.' : 'A.T.M.'; + } + }, + calendar : { + sameDay : '[Hodiaŭ je] LT', + nextDay : '[Morgaŭ je] LT', + nextWeek : 'dddd [je] LT', + lastDay : '[Hieraŭ je] LT', + lastWeek : '[pasinta] dddd [je] LT', + sameElse : 'L' + }, + relativeTime : { + future : "je %s", + past : "antaŭ %s", + s : "sekundoj", + m : "minuto", + mm : "%d minutoj", + h : "horo", + hh : "%d horoj", + d : "tago",//ne 'diurno', ĉar estas uzita por proksimumo + dd : "%d tagoj", + M : "monato", + MM : "%d monatoj", + y : "jaro", + yy : "%d jaroj" + }, + ordinal : "%da", + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : spanish (es) +// author : Julio Napurí : https://github.com/julionc + +(function (factory) { + factory(moment); +}(function (moment) { + var monthsShortDot = "ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"), + monthsShort = "ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_"); + + return moment.lang('es', { + months : "enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShort[m.month()]; + } else { + return monthsShortDot[m.month()]; + } + }, + weekdays : "domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"), + weekdaysShort : "dom._lun._mar._mié._jue._vie._sáb.".split("_"), + weekdaysMin : "Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD/MM/YYYY", + LL : "D [de] MMMM [del] YYYY", + LLL : "D [de] MMMM [del] YYYY LT", + LLLL : "dddd, D [de] MMMM [del] YYYY LT" + }, + calendar : { + sameDay : function () { + return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextDay : function () { + return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastDay : function () { + return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : "en %s", + past : "hace %s", + s : "unos segundos", + m : "un minuto", + mm : "%d minutos", + h : "una hora", + hh : "%d horas", + d : "un día", + dd : "%d días", + M : "un mes", + MM : "%d meses", + y : "un año", + yy : "%d años" + }, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : estonian (et) +// author : Henry Kehlmann : https://github.com/madhenry +// improvements : Illimar Tambek : https://github.com/ragulka + +(function (factory) { + factory(moment); +}(function (moment) { + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 's' : ['mõne sekundi', 'mõni sekund', 'paar sekundit'], + 'm' : ['ühe minuti', 'üks minut'], + 'mm': [number + ' minuti', number + ' minutit'], + 'h' : ['ühe tunni', 'tund aega', 'üks tund'], + 'hh': [number + ' tunni', number + ' tundi'], + 'd' : ['ühe päeva', 'üks päev'], + 'M' : ['kuu aja', 'kuu aega', 'üks kuu'], + 'MM': [number + ' kuu', number + ' kuud'], + 'y' : ['ühe aasta', 'aasta', 'üks aasta'], + 'yy': [number + ' aasta', number + ' aastat'] + }; + if (withoutSuffix) { + return format[key][2] ? format[key][2] : format[key][1]; + } + return isFuture ? format[key][0] : format[key][1]; + } + + return moment.lang('et', { + months : "jaanuar_veebruar_märts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"), + monthsShort : "jaan_veebr_märts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"), + weekdays : "pühapäev_esmaspäev_teisipäev_kolmapäev_neljapäev_reede_laupäev".split("_"), + weekdaysShort : "P_E_T_K_N_R_L".split("_"), + weekdaysMin : "P_E_T_K_N_R_L".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD.MM.YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd, D. MMMM YYYY LT" + }, + calendar : { + sameDay : '[Täna,] LT', + nextDay : '[Homme,] LT', + nextWeek : '[Järgmine] dddd LT', + lastDay : '[Eile,] LT', + lastWeek : '[Eelmine] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s pärast", + past : "%s tagasi", + s : processRelativeTime, + m : processRelativeTime, + mm : processRelativeTime, + h : processRelativeTime, + hh : processRelativeTime, + d : processRelativeTime, + dd : '%d päeva', + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : euskara (eu) +// author : Eneko Illarramendi : https://github.com/eillarra + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('eu', { + months : "urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua".split("_"), + monthsShort : "urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.".split("_"), + weekdays : "igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata".split("_"), + weekdaysShort : "ig._al._ar._az._og._ol._lr.".split("_"), + weekdaysMin : "ig_al_ar_az_og_ol_lr".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "YYYY-MM-DD", + LL : "YYYY[ko] MMMM[ren] D[a]", + LLL : "YYYY[ko] MMMM[ren] D[a] LT", + LLLL : "dddd, YYYY[ko] MMMM[ren] D[a] LT", + l : "YYYY-M-D", + ll : "YYYY[ko] MMM D[a]", + lll : "YYYY[ko] MMM D[a] LT", + llll : "ddd, YYYY[ko] MMM D[a] LT" + }, + calendar : { + sameDay : '[gaur] LT[etan]', + nextDay : '[bihar] LT[etan]', + nextWeek : 'dddd LT[etan]', + lastDay : '[atzo] LT[etan]', + lastWeek : '[aurreko] dddd LT[etan]', + sameElse : 'L' + }, + relativeTime : { + future : "%s barru", + past : "duela %s", + s : "segundo batzuk", + m : "minutu bat", + mm : "%d minutu", + h : "ordu bat", + hh : "%d ordu", + d : "egun bat", + dd : "%d egun", + M : "hilabete bat", + MM : "%d hilabete", + y : "urte bat", + yy : "%d urte" + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Persian Language +// author : Ebrahim Byagowi : https://github.com/ebraminio + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '۱', + '2': '۲', + '3': '۳', + '4': '۴', + '5': '۵', + '6': '۶', + '7': '۷', + '8': '۸', + '9': '۹', + '0': '۰' + }, numberMap = { + '۱': '1', + '۲': '2', + '۳': '3', + '۴': '4', + '۵': '5', + '۶': '6', + '۷': '7', + '۸': '8', + '۹': '9', + '۰': '0' + }; + + return moment.lang('fa', { + months : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), + monthsShort : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), + weekdays : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), + weekdaysShort : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), + weekdaysMin : 'ی_د_س_چ_پ_ج_ش'.split('_'), + longDateFormat : { + LT : 'HH:mm', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY LT', + LLLL : 'dddd, D MMMM YYYY LT' + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return "قبل از ظهر"; + } else { + return "بعد از ظهر"; + } + }, + calendar : { + sameDay : '[امروز ساعت] LT', + nextDay : '[فردا ساعت] LT', + nextWeek : 'dddd [ساعت] LT', + lastDay : '[دیروز ساعت] LT', + lastWeek : 'dddd [پیش] [ساعت] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'در %s', + past : '%s پیش', + s : 'چندین ثانیه', + m : 'یک دقیقه', + mm : '%d دقیقه', + h : 'یک ساعت', + hh : '%d ساعت', + d : 'یک روز', + dd : '%d روز', + M : 'یک ماه', + MM : '%d ماه', + y : 'یک سال', + yy : '%d سال' + }, + preparse: function (string) { + return string.replace(/[۰-۹]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + ordinal : '%dم', + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : finnish (fi) +// author : Tarmo Aidantausta : https://github.com/bleadof + +(function (factory) { + factory(moment); +}(function (moment) { + var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '), + numbersFuture = ['nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden', + numbersPast[7], numbersPast[8], numbersPast[9]]; + + function translate(number, withoutSuffix, key, isFuture) { + var result = ""; + switch (key) { + case 's': + return isFuture ? 'muutaman sekunnin' : 'muutama sekunti'; + case 'm': + return isFuture ? 'minuutin' : 'minuutti'; + case 'mm': + result = isFuture ? 'minuutin' : 'minuuttia'; + break; + case 'h': + return isFuture ? 'tunnin' : 'tunti'; + case 'hh': + result = isFuture ? 'tunnin' : 'tuntia'; + break; + case 'd': + return isFuture ? 'päivän' : 'päivä'; + case 'dd': + result = isFuture ? 'päivän' : 'päivää'; + break; + case 'M': + return isFuture ? 'kuukauden' : 'kuukausi'; + case 'MM': + result = isFuture ? 'kuukauden' : 'kuukautta'; + break; + case 'y': + return isFuture ? 'vuoden' : 'vuosi'; + case 'yy': + result = isFuture ? 'vuoden' : 'vuotta'; + break; + } + result = verbalNumber(number, isFuture) + " " + result; + return result; + } + + function verbalNumber(number, isFuture) { + return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number; + } + + return moment.lang('fi', { + months : "tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"), + monthsShort : "tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"), + weekdays : "sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"), + weekdaysShort : "su_ma_ti_ke_to_pe_la".split("_"), + weekdaysMin : "su_ma_ti_ke_to_pe_la".split("_"), + longDateFormat : { + LT : "HH.mm", + L : "DD.MM.YYYY", + LL : "Do MMMM[ta] YYYY", + LLL : "Do MMMM[ta] YYYY, [klo] LT", + LLLL : "dddd, Do MMMM[ta] YYYY, [klo] LT", + l : "D.M.YYYY", + ll : "Do MMM YYYY", + lll : "Do MMM YYYY, [klo] LT", + llll : "ddd, Do MMM YYYY, [klo] LT" + }, + calendar : { + sameDay : '[tänään] [klo] LT', + nextDay : '[huomenna] [klo] LT', + nextWeek : 'dddd [klo] LT', + lastDay : '[eilen] [klo] LT', + lastWeek : '[viime] dddd[na] [klo] LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s päästä", + past : "%s sitten", + s : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + ordinal : "%d.", + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : faroese (fo) +// author : Ragnar Johannesen : https://github.com/ragnar123 + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('fo', { + months : "januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember".split("_"), + monthsShort : "jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"), + weekdays : "sunnudagur_mánadagur_týsdagur_mikudagur_hósdagur_fríggjadagur_leygardagur".split("_"), + weekdaysShort : "sun_mán_týs_mik_hós_frí_ley".split("_"), + weekdaysMin : "su_má_tý_mi_hó_fr_le".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D. MMMM, YYYY LT" + }, + calendar : { + sameDay : '[Í dag kl.] LT', + nextDay : '[Í morgin kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[Í gjár kl.] LT', + lastWeek : '[síðstu] dddd [kl] LT', + sameElse : 'L' + }, + relativeTime : { + future : "um %s", + past : "%s síðani", + s : "fá sekund", + m : "ein minutt", + mm : "%d minuttir", + h : "ein tími", + hh : "%d tímar", + d : "ein dagur", + dd : "%d dagar", + M : "ein mánaði", + MM : "%d mánaðir", + y : "eitt ár", + yy : "%d ár" + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : canadian french (fr-ca) +// author : Jonathan Abourbih : https://github.com/jonbca + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('fr-ca', { + months : "janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"), + monthsShort : "janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"), + weekdays : "dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"), + weekdaysShort : "dim._lun._mar._mer._jeu._ven._sam.".split("_"), + weekdaysMin : "Di_Lu_Ma_Me_Je_Ve_Sa".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "YYYY-MM-DD", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: "[Aujourd'hui à] LT", + nextDay: '[Demain à] LT', + nextWeek: 'dddd [à] LT', + lastDay: '[Hier à] LT', + lastWeek: 'dddd [dernier à] LT', + sameElse: 'L' + }, + relativeTime : { + future : "dans %s", + past : "il y a %s", + s : "quelques secondes", + m : "une minute", + mm : "%d minutes", + h : "une heure", + hh : "%d heures", + d : "un jour", + dd : "%d jours", + M : "un mois", + MM : "%d mois", + y : "un an", + yy : "%d ans" + }, + ordinal : function (number) { + return number + (number === 1 ? 'er' : ''); + } + }); +})); +// moment.js language configuration +// language : french (fr) +// author : John Fischer : https://github.com/jfroffice + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('fr', { + months : "janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"), + monthsShort : "janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"), + weekdays : "dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"), + weekdaysShort : "dim._lun._mar._mer._jeu._ven._sam.".split("_"), + weekdaysMin : "Di_Lu_Ma_Me_Je_Ve_Sa".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: "[Aujourd'hui à] LT", + nextDay: '[Demain à] LT', + nextWeek: 'dddd [à] LT', + lastDay: '[Hier à] LT', + lastWeek: 'dddd [dernier à] LT', + sameElse: 'L' + }, + relativeTime : { + future : "dans %s", + past : "il y a %s", + s : "quelques secondes", + m : "une minute", + mm : "%d minutes", + h : "une heure", + hh : "%d heures", + d : "un jour", + dd : "%d jours", + M : "un mois", + MM : "%d mois", + y : "un an", + yy : "%d ans" + }, + ordinal : function (number) { + return number + (number === 1 ? 'er' : ''); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : galician (gl) +// author : Juan G. Hurtado : https://github.com/juanghurtado + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('gl', { + months : "Xaneiro_Febreiro_Marzo_Abril_Maio_Xuño_Xullo_Agosto_Setembro_Outubro_Novembro_Decembro".split("_"), + monthsShort : "Xan._Feb._Mar._Abr._Mai._Xuñ._Xul._Ago._Set._Out._Nov._Dec.".split("_"), + weekdays : "Domingo_Luns_Martes_Mércores_Xoves_Venres_Sábado".split("_"), + weekdaysShort : "Dom._Lun._Mar._Mér._Xov._Ven._Sáb.".split("_"), + weekdaysMin : "Do_Lu_Ma_Mé_Xo_Ve_Sá".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay : function () { + return '[hoxe ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; + }, + nextDay : function () { + return '[mañá ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; + }, + nextWeek : function () { + return 'dddd [' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; + }, + lastDay : function () { + return '[onte ' + ((this.hours() !== 1) ? 'á' : 'a') + '] LT'; + }, + lastWeek : function () { + return '[o] dddd [pasado ' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : function (str) { + if (str === "uns segundos") { + return "nuns segundos"; + } + return "en " + str; + }, + past : "hai %s", + s : "uns segundos", + m : "un minuto", + mm : "%d minutos", + h : "unha hora", + hh : "%d horas", + d : "un día", + dd : "%d días", + M : "un mes", + MM : "%d meses", + y : "un ano", + yy : "%d anos" + }, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Hebrew (he) +// author : Tomer Cohen : https://github.com/tomer +// author : Moshe Simantov : https://github.com/DevelopmentIL +// author : Tal Ater : https://github.com/TalAter + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('he', { + months : "ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר".split("_"), + monthsShort : "ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳".split("_"), + weekdays : "ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת".split("_"), + weekdaysShort : "א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳".split("_"), + weekdaysMin : "א_ב_ג_ד_ה_ו_ש".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D [ב]MMMM YYYY", + LLL : "D [ב]MMMM YYYY LT", + LLLL : "dddd, D [ב]MMMM YYYY LT", + l : "D/M/YYYY", + ll : "D MMM YYYY", + lll : "D MMM YYYY LT", + llll : "ddd, D MMM YYYY LT" + }, + calendar : { + sameDay : '[היום ב־]LT', + nextDay : '[מחר ב־]LT', + nextWeek : 'dddd [בשעה] LT', + lastDay : '[אתמול ב־]LT', + lastWeek : '[ביום] dddd [האחרון בשעה] LT', + sameElse : 'L' + }, + relativeTime : { + future : "בעוד %s", + past : "לפני %s", + s : "מספר שניות", + m : "דקה", + mm : "%d דקות", + h : "שעה", + hh : function (number) { + if (number === 2) { + return "שעתיים"; + } + return number + " שעות"; + }, + d : "יום", + dd : function (number) { + if (number === 2) { + return "יומיים"; + } + return number + " ימים"; + }, + M : "חודש", + MM : function (number) { + if (number === 2) { + return "חודשיים"; + } + return number + " חודשים"; + }, + y : "שנה", + yy : function (number) { + if (number === 2) { + return "שנתיים"; + } + return number + " שנים"; + } + } + }); +})); +// moment.js language configuration +// language : hindi (hi) +// author : Mayank Singhal : https://github.com/mayanksinghal + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' + }, + numberMap = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' + }; + + return moment.lang('hi', { + months : 'जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर'.split("_"), + monthsShort : 'जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.'.split("_"), + weekdays : 'रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split("_"), + weekdaysShort : 'रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि'.split("_"), + weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split("_"), + longDateFormat : { + LT : "A h:mm बजे", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY, LT", + LLLL : "dddd, D MMMM YYYY, LT" + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[कल] LT', + nextWeek : 'dddd, LT', + lastDay : '[कल] LT', + lastWeek : '[पिछले] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s में", + past : "%s पहले", + s : "कुछ ही क्षण", + m : "एक मिनट", + mm : "%d मिनट", + h : "एक घंटा", + hh : "%d घंटे", + d : "एक दिन", + dd : "%d दिन", + M : "एक महीने", + MM : "%d महीने", + y : "एक वर्ष", + yy : "%d वर्ष" + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + // Hindi notation for meridiems are quite fuzzy in practice. While there exists + // a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi. + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return "रात"; + } else if (hour < 10) { + return "सुबह"; + } else if (hour < 17) { + return "दोपहर"; + } else if (hour < 20) { + return "शाम"; + } else { + return "रात"; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : hrvatski (hr) +// author : Bojan Marković : https://github.com/bmarkovic + +// based on (sl) translation by Robert Sedovšek + +(function (factory) { + factory(moment); +}(function (moment) { + + function translate(number, withoutSuffix, key) { + var result = number + " "; + switch (key) { + case 'm': + return withoutSuffix ? 'jedna minuta' : 'jedne minute'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minuta'; + } + return result; + case 'h': + return withoutSuffix ? 'jedan sat' : 'jednog sata'; + case 'hh': + if (number === 1) { + result += 'sat'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sata'; + } else { + result += 'sati'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dana'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mjesec'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'mjeseca'; + } else { + result += 'mjeseci'; + } + return result; + case 'yy': + if (number === 1) { + result += 'godina'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'godine'; + } else { + result += 'godina'; + } + return result; + } + } + + return moment.lang('hr', { + months : "sječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac".split("_"), + monthsShort : "sje._vel._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.".split("_"), + weekdays : "nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"), + weekdaysShort : "ned._pon._uto._sri._čet._pet._sub.".split("_"), + weekdaysMin : "ne_po_ut_sr_če_pe_su".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD. MM. YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd, D. MMMM YYYY LT" + }, + calendar : { + sameDay : '[danas u] LT', + nextDay : '[sutra u] LT', + + nextWeek : function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[jučer u] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prošli] dddd [u] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : "za %s", + past : "prije %s", + s : "par sekundi", + m : translate, + mm : translate, + h : translate, + hh : translate, + d : "dan", + dd : translate, + M : "mjesec", + MM : translate, + y : "godinu", + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : hungarian (hu) +// author : Adam Brunner : https://github.com/adambrunner + +(function (factory) { + factory(moment); +}(function (moment) { + var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' '); + + function translate(number, withoutSuffix, key, isFuture) { + var num = number, + suffix; + + switch (key) { + case 's': + return (isFuture || withoutSuffix) ? 'néhány másodperc' : 'néhány másodperce'; + case 'm': + return 'egy' + (isFuture || withoutSuffix ? ' perc' : ' perce'); + case 'mm': + return num + (isFuture || withoutSuffix ? ' perc' : ' perce'); + case 'h': + return 'egy' + (isFuture || withoutSuffix ? ' óra' : ' órája'); + case 'hh': + return num + (isFuture || withoutSuffix ? ' óra' : ' órája'); + case 'd': + return 'egy' + (isFuture || withoutSuffix ? ' nap' : ' napja'); + case 'dd': + return num + (isFuture || withoutSuffix ? ' nap' : ' napja'); + case 'M': + return 'egy' + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); + case 'MM': + return num + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); + case 'y': + return 'egy' + (isFuture || withoutSuffix ? ' év' : ' éve'); + case 'yy': + return num + (isFuture || withoutSuffix ? ' év' : ' éve'); + } + + return ''; + } + + function week(isFuture) { + return (isFuture ? '' : '[múlt] ') + '[' + weekEndings[this.day()] + '] LT[-kor]'; + } + + return moment.lang('hu', { + months : "január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"), + monthsShort : "jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"), + weekdays : "vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"), + weekdaysShort : "vas_hét_kedd_sze_csüt_pén_szo".split("_"), + weekdaysMin : "v_h_k_sze_cs_p_szo".split("_"), + longDateFormat : { + LT : "H:mm", + L : "YYYY.MM.DD.", + LL : "YYYY. MMMM D.", + LLL : "YYYY. MMMM D., LT", + LLLL : "YYYY. MMMM D., dddd LT" + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower === true ? 'de' : 'DE'; + } else { + return isLower === true ? 'du' : 'DU'; + } + }, + calendar : { + sameDay : '[ma] LT[-kor]', + nextDay : '[holnap] LT[-kor]', + nextWeek : function () { + return week.call(this, true); + }, + lastDay : '[tegnap] LT[-kor]', + lastWeek : function () { + return week.call(this, false); + }, + sameElse : 'L' + }, + relativeTime : { + future : "%s múlva", + past : "%s", + s : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Armenian (hy-am) +// author : Armendarabyan : https://github.com/armendarabyan + +(function (factory) { + factory(moment); +}(function (moment) { + + function monthsCaseReplace(m, format) { + var months = { + 'nominative': 'հունվար_փետրվար_մարտ_ապրիլ_մայիս_հունիս_հուլիս_օգոստոս_սեպտեմբեր_հոկտեմբեր_նոյեմբեր_դեկտեմբեր'.split('_'), + 'accusative': 'հունվարի_փետրվարի_մարտի_ապրիլի_մայիսի_հունիսի_հուլիսի_օգոստոսի_սեպտեմբերի_հոկտեմբերի_նոյեմբերի_դեկտեմբերի'.split('_') + }, + + nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? + 'accusative' : + 'nominative'; + + return months[nounCase][m.month()]; + } + + function monthsShortCaseReplace(m, format) { + var monthsShort = 'հնվ_փտր_մրտ_ապր_մյս_հնս_հլս_օգս_սպտ_հկտ_նմբ_դկտ'.split('_'); + + return monthsShort[m.month()]; + } + + function weekdaysCaseReplace(m, format) { + var weekdays = 'կիրակի_երկուշաբթի_երեքշաբթի_չորեքշաբթի_հինգշաբթի_ուրբաթ_շաբաթ'.split('_'); + + return weekdays[m.day()]; + } + + return moment.lang('hy-am', { + months : monthsCaseReplace, + monthsShort : monthsShortCaseReplace, + weekdays : weekdaysCaseReplace, + weekdaysShort : "կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ".split("_"), + weekdaysMin : "կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY թ.", + LLL : "D MMMM YYYY թ., LT", + LLLL : "dddd, D MMMM YYYY թ., LT" + }, + calendar : { + sameDay: '[այսօր] LT', + nextDay: '[վաղը] LT', + lastDay: '[երեկ] LT', + nextWeek: function () { + return 'dddd [օրը ժամը] LT'; + }, + lastWeek: function () { + return '[անցած] dddd [օրը ժամը] LT'; + }, + sameElse: 'L' + }, + relativeTime : { + future : "%s հետո", + past : "%s առաջ", + s : "մի քանի վայրկյան", + m : "րոպե", + mm : "%d րոպե", + h : "ժամ", + hh : "%d ժամ", + d : "օր", + dd : "%d օր", + M : "ամիս", + MM : "%d ամիս", + y : "տարի", + yy : "%d տարի" + }, + + meridiem : function (hour) { + if (hour < 4) { + return "գիշերվա"; + } else if (hour < 12) { + return "առավոտվա"; + } else if (hour < 17) { + return "ցերեկվա"; + } else { + return "երեկոյան"; + } + }, + + ordinal: function (number, period) { + switch (period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + if (number === 1) { + return number + '-ին'; + } + return number + '-րդ'; + default: + return number; + } + }, + + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Bahasa Indonesia (id) +// author : Mohammad Satrio Utomo : https://github.com/tyok +// reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('id', { + months : "Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"), + monthsShort : "Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"), + weekdays : "Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"), + weekdaysShort : "Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"), + weekdaysMin : "Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"), + longDateFormat : { + LT : "HH.mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY [pukul] LT", + LLLL : "dddd, D MMMM YYYY [pukul] LT" + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'siang'; + } else if (hours < 19) { + return 'sore'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Besok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kemarin pukul] LT', + lastWeek : 'dddd [lalu pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : "dalam %s", + past : "%s yang lalu", + s : "beberapa detik", + m : "semenit", + mm : "%d menit", + h : "sejam", + hh : "%d jam", + d : "sehari", + dd : "%d hari", + M : "sebulan", + MM : "%d bulan", + y : "setahun", + yy : "%d tahun" + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : icelandic (is) +// author : Hinrik Örn Sigurðsson : https://github.com/hinrik + +(function (factory) { + factory(moment); +}(function (moment) { + function plural(n) { + if (n % 100 === 11) { + return true; + } else if (n % 10 === 1) { + return false; + } + return true; + } + + function translate(number, withoutSuffix, key, isFuture) { + var result = number + " "; + switch (key) { + case 's': + return withoutSuffix || isFuture ? 'nokkrar sekúndur' : 'nokkrum sekúndum'; + case 'm': + return withoutSuffix ? 'mínúta' : 'mínútu'; + case 'mm': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'mínútur' : 'mínútum'); + } else if (withoutSuffix) { + return result + 'mínúta'; + } + return result + 'mínútu'; + case 'hh': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'klukkustundir' : 'klukkustundum'); + } + return result + 'klukkustund'; + case 'd': + if (withoutSuffix) { + return 'dagur'; + } + return isFuture ? 'dag' : 'degi'; + case 'dd': + if (plural(number)) { + if (withoutSuffix) { + return result + 'dagar'; + } + return result + (isFuture ? 'daga' : 'dögum'); + } else if (withoutSuffix) { + return result + 'dagur'; + } + return result + (isFuture ? 'dag' : 'degi'); + case 'M': + if (withoutSuffix) { + return 'mánuður'; + } + return isFuture ? 'mánuð' : 'mánuði'; + case 'MM': + if (plural(number)) { + if (withoutSuffix) { + return result + 'mánuðir'; + } + return result + (isFuture ? 'mánuði' : 'mánuðum'); + } else if (withoutSuffix) { + return result + 'mánuður'; + } + return result + (isFuture ? 'mánuð' : 'mánuði'); + case 'y': + return withoutSuffix || isFuture ? 'ár' : 'ári'; + case 'yy': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'ár' : 'árum'); + } + return result + (withoutSuffix || isFuture ? 'ár' : 'ári'); + } + } + + return moment.lang('is', { + months : "janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"), + monthsShort : "jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"), + weekdays : "sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"), + weekdaysShort : "sun_mán_þri_mið_fim_fös_lau".split("_"), + weekdaysMin : "Su_Má_Þr_Mi_Fi_Fö_La".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD/MM/YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY [kl.] LT", + LLLL : "dddd, D. MMMM YYYY [kl.] LT" + }, + calendar : { + sameDay : '[í dag kl.] LT', + nextDay : '[á morgun kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[í gær kl.] LT', + lastWeek : '[síðasta] dddd [kl.] LT', + sameElse : 'L' + }, + relativeTime : { + future : "eftir %s", + past : "fyrir %s síðan", + s : translate, + m : translate, + mm : translate, + h : "klukkustund", + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : italian (it) +// author : Lorenzo : https://github.com/aliem +// author: Mattia Larentis: https://github.com/nostalgiaz + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('it', { + months : "gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"), + monthsShort : "gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"), + weekdays : "Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"), + weekdaysShort : "Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"), + weekdaysMin : "D_L_Ma_Me_G_V_S".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay: '[Oggi alle] LT', + nextDay: '[Domani alle] LT', + nextWeek: 'dddd [alle] LT', + lastDay: '[Ieri alle] LT', + lastWeek: '[lo scorso] dddd [alle] LT', + sameElse: 'L' + }, + relativeTime : { + future : function (s) { + return ((/^[0-9].+$/).test(s) ? "tra" : "in") + " " + s; + }, + past : "%s fa", + s : "alcuni secondi", + m : "un minuto", + mm : "%d minuti", + h : "un'ora", + hh : "%d ore", + d : "un giorno", + dd : "%d giorni", + M : "un mese", + MM : "%d mesi", + y : "un anno", + yy : "%d anni" + }, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : japanese (ja) +// author : LI Long : https://github.com/baryon + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('ja', { + months : "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"), + monthsShort : "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"), + weekdays : "日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"), + weekdaysShort : "日_月_火_水_木_金_土".split("_"), + weekdaysMin : "日_月_火_水_木_金_土".split("_"), + longDateFormat : { + LT : "Ah時m分", + L : "YYYY/MM/DD", + LL : "YYYY年M月D日", + LLL : "YYYY年M月D日LT", + LLLL : "YYYY年M月D日LT dddd" + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return "午前"; + } else { + return "午後"; + } + }, + calendar : { + sameDay : '[今日] LT', + nextDay : '[明日] LT', + nextWeek : '[来週]dddd LT', + lastDay : '[昨日] LT', + lastWeek : '[前週]dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s後", + past : "%s前", + s : "数秒", + m : "1分", + mm : "%d分", + h : "1時間", + hh : "%d時間", + d : "1日", + dd : "%d日", + M : "1ヶ月", + MM : "%dヶ月", + y : "1年", + yy : "%d年" + } + }); +})); +// moment.js language configuration +// language : Georgian (ka) +// author : Irakli Janiashvili : https://github.com/irakli-janiashvili + +(function (factory) { + factory(moment); +}(function (moment) { + + function monthsCaseReplace(m, format) { + var months = { + 'nominative': 'იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი'.split('_'), + 'accusative': 'იანვარს_თებერვალს_მარტს_აპრილის_მაისს_ივნისს_ივლისს_აგვისტს_სექტემბერს_ოქტომბერს_ნოემბერს_დეკემბერს'.split('_') + }, + + nounCase = (/D[oD] *MMMM?/).test(format) ? + 'accusative' : + 'nominative'; + + return months[nounCase][m.month()]; + } + + function weekdaysCaseReplace(m, format) { + var weekdays = { + 'nominative': 'კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი'.split('_'), + 'accusative': 'კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს'.split('_') + }, + + nounCase = (/(წინა|შემდეგ)/).test(format) ? + 'accusative' : + 'nominative'; + + return weekdays[nounCase][m.day()]; + } + + return moment.lang('ka', { + months : monthsCaseReplace, + monthsShort : "იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ".split("_"), + weekdays : weekdaysCaseReplace, + weekdaysShort : "კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ".split("_"), + weekdaysMin : "კვ_ორ_სა_ოთ_ხუ_პა_შა".split("_"), + longDateFormat : { + LT : "h:mm A", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[დღეს] LT[-ზე]', + nextDay : '[ხვალ] LT[-ზე]', + lastDay : '[გუშინ] LT[-ზე]', + nextWeek : '[შემდეგ] dddd LT[-ზე]', + lastWeek : '[წინა] dddd LT-ზე', + sameElse : 'L' + }, + relativeTime : { + future : function (s) { + return (/(წამი|წუთი|საათი|წელი)/).test(s) ? + s.replace(/ი$/, "ში") : + s + "ში"; + }, + past : function (s) { + if ((/(წამი|წუთი|საათი|დღე|თვე)/).test(s)) { + return s.replace(/(ი|ე)$/, "ის წინ"); + } + if ((/წელი/).test(s)) { + return s.replace(/წელი$/, "წლის წინ"); + } + }, + s : "რამდენიმე წამი", + m : "წუთი", + mm : "%d წუთი", + h : "საათი", + hh : "%d საათი", + d : "დღე", + dd : "%d დღე", + M : "თვე", + MM : "%d თვე", + y : "წელი", + yy : "%d წელი" + }, + ordinal : function (number) { + if (number === 0) { + return number; + } + + if (number === 1) { + return number + "-ლი"; + } + + if ((number < 20) || (number <= 100 && (number % 20 === 0)) || (number % 100 === 0)) { + return "მე-" + number; + } + + return number + "-ე"; + }, + week : { + dow : 1, + doy : 7 + } + }); +})); +// moment.js language configuration +// language : khmer (km) +// author : Kruy Vanna : https://github.com/kruyvanna + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('km', { + months: "មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split("_"), + monthsShort: "មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split("_"), + weekdays: "អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"), + weekdaysShort: "អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"), + weekdaysMin: "អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"), + longDateFormat: { + LT: "HH:mm", + L: "DD/MM/YYYY", + LL: "D MMMM YYYY", + LLL: "D MMMM YYYY LT", + LLLL: "dddd, D MMMM YYYY LT" + }, + calendar: { + sameDay: '[ថ្ងៃនៈ ម៉ោង] LT', + nextDay: '[ស្អែក ម៉ោង] LT', + nextWeek: 'dddd [ម៉ោង] LT', + lastDay: '[ម្សិលមិញ ម៉ោង] LT', + lastWeek: 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', + sameElse: 'L' + }, + relativeTime: { + future: "%sទៀត", + past: "%sមុន", + s: "ប៉ុន្មានវិនាទី", + m: "មួយនាទី", + mm: "%d នាទី", + h: "មួយម៉ោង", + hh: "%d ម៉ោង", + d: "មួយថ្ងៃ", + dd: "%d ថ្ងៃ", + M: "មួយខែ", + MM: "%d ខែ", + y: "មួយឆ្នាំ", + yy: "%d ឆ្នាំ" + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : korean (ko) +// +// authors +// +// - Kyungwook, Park : https://github.com/kyungw00k +// - Jeeeyul Lee <jeeeyul@gmail.com> +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('ko', { + months : "1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"), + monthsShort : "1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"), + weekdays : "일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"), + weekdaysShort : "일_월_화_수_목_금_토".split("_"), + weekdaysMin : "일_월_화_수_목_금_토".split("_"), + longDateFormat : { + LT : "A h시 mm분", + L : "YYYY.MM.DD", + LL : "YYYY년 MMMM D일", + LLL : "YYYY년 MMMM D일 LT", + LLLL : "YYYY년 MMMM D일 dddd LT" + }, + meridiem : function (hour, minute, isUpper) { + return hour < 12 ? '오전' : '오후'; + }, + calendar : { + sameDay : '오늘 LT', + nextDay : '내일 LT', + nextWeek : 'dddd LT', + lastDay : '어제 LT', + lastWeek : '지난주 dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s 후", + past : "%s 전", + s : "몇초", + ss : "%d초", + m : "일분", + mm : "%d분", + h : "한시간", + hh : "%d시간", + d : "하루", + dd : "%d일", + M : "한달", + MM : "%d달", + y : "일년", + yy : "%d년" + }, + ordinal : '%d일', + meridiemParse : /(오전|오후)/, + isPM : function (token) { + return token === "오후"; + } + }); +})); +// moment.js language configuration +// language : Luxembourgish (lb) +// author : mweimerskirch : https://github.com/mweimerskirch + +// Note: Luxembourgish has a very particular phonological rule ("Eifeler Regel") that causes the +// deletion of the final "n" in certain contexts. That's what the "eifelerRegelAppliesToWeekday" +// and "eifelerRegelAppliesToNumber" methods are meant for + +(function (factory) { + factory(moment); +}(function (moment) { + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eng Minutt', 'enger Minutt'], + 'h': ['eng Stonn', 'enger Stonn'], + 'd': ['een Dag', 'engem Dag'], + 'dd': [number + ' Deeg', number + ' Deeg'], + 'M': ['ee Mount', 'engem Mount'], + 'MM': [number + ' Méint', number + ' Méint'], + 'y': ['ee Joer', 'engem Joer'], + 'yy': [number + ' Joer', number + ' Joer'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + function processFutureTime(string) { + var number = string.substr(0, string.indexOf(' ')); + if (eifelerRegelAppliesToNumber(number)) { + return "a " + string; + } + return "an " + string; + } + + function processPastTime(string) { + var number = string.substr(0, string.indexOf(' ')); + if (eifelerRegelAppliesToNumber(number)) { + return "viru " + string; + } + return "virun " + string; + } + + function processLastWeek(string1) { + var weekday = this.format('d'); + if (eifelerRegelAppliesToWeekday(weekday)) { + return '[Leschte] dddd [um] LT'; + } + return '[Leschten] dddd [um] LT'; + } + + /** + * Returns true if the word before the given week day loses the "-n" ending. + * e.g. "Leschten Dënschdeg" but "Leschte Méindeg" + * + * @param weekday {integer} + * @returns {boolean} + */ + function eifelerRegelAppliesToWeekday(weekday) { + weekday = parseInt(weekday, 10); + switch (weekday) { + case 0: // Sonndeg + case 1: // Méindeg + case 3: // Mëttwoch + case 5: // Freideg + case 6: // Samschdeg + return true; + default: // 2 Dënschdeg, 4 Donneschdeg + return false; + } + } + + /** + * Returns true if the word before the given number loses the "-n" ending. + * e.g. "an 10 Deeg" but "a 5 Deeg" + * + * @param number {integer} + * @returns {boolean} + */ + function eifelerRegelAppliesToNumber(number) { + number = parseInt(number, 10); + if (isNaN(number)) { + return false; + } + if (number < 0) { + // Negative Number --> always true + return true; + } else if (number < 10) { + // Only 1 digit + if (4 <= number && number <= 7) { + return true; + } + return false; + } else if (number < 100) { + // 2 digits + var lastDigit = number % 10, firstDigit = number / 10; + if (lastDigit === 0) { + return eifelerRegelAppliesToNumber(firstDigit); + } + return eifelerRegelAppliesToNumber(lastDigit); + } else if (number < 10000) { + // 3 or 4 digits --> recursively check first digit + while (number >= 10) { + number = number / 10; + } + return eifelerRegelAppliesToNumber(number); + } else { + // Anything larger than 4 digits: recursively check first n-3 digits + number = number / 1000; + return eifelerRegelAppliesToNumber(number); + } + } + + return moment.lang('lb', { + months: "Januar_Februar_Mäerz_Abrëll_Mee_Juni_Juli_August_September_Oktober_November_Dezember".split("_"), + monthsShort: "Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"), + weekdays: "Sonndeg_Méindeg_Dënschdeg_Mëttwoch_Donneschdeg_Freideg_Samschdeg".split("_"), + weekdaysShort: "So._Mé._Dë._Më._Do._Fr._Sa.".split("_"), + weekdaysMin: "So_Mé_Dë_Më_Do_Fr_Sa".split("_"), + longDateFormat: { + LT: "H:mm [Auer]", + L: "DD.MM.YYYY", + LL: "D. MMMM YYYY", + LLL: "D. MMMM YYYY LT", + LLLL: "dddd, D. MMMM YYYY LT" + }, + calendar: { + sameDay: "[Haut um] LT", + sameElse: "L", + nextDay: '[Muer um] LT', + nextWeek: 'dddd [um] LT', + lastDay: '[Gëschter um] LT', + lastWeek: processLastWeek + }, + relativeTime: { + future: processFutureTime, + past: processPastTime, + s: "e puer Sekonnen", + m: processRelativeTime, + mm: "%d Minutten", + h: processRelativeTime, + hh: "%d Stonnen", + d: processRelativeTime, + dd: processRelativeTime, + M: processRelativeTime, + MM: processRelativeTime, + y: processRelativeTime, + yy: processRelativeTime + }, + ordinal: '%d.', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Lithuanian (lt) +// author : Mindaugas Mozūras : https://github.com/mmozuras + +(function (factory) { + factory(moment); +}(function (moment) { + var units = { + "m" : "minutė_minutės_minutę", + "mm": "minutės_minučių_minutes", + "h" : "valanda_valandos_valandą", + "hh": "valandos_valandų_valandas", + "d" : "diena_dienos_dieną", + "dd": "dienos_dienų_dienas", + "M" : "mėnuo_mėnesio_mėnesį", + "MM": "mėnesiai_mėnesių_mėnesius", + "y" : "metai_metų_metus", + "yy": "metai_metų_metus" + }, + weekDays = "sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis".split("_"); + + function translateSeconds(number, withoutSuffix, key, isFuture) { + if (withoutSuffix) { + return "kelios sekundės"; + } else { + return isFuture ? "kelių sekundžių" : "kelias sekundes"; + } + } + + function translateSingular(number, withoutSuffix, key, isFuture) { + return withoutSuffix ? forms(key)[0] : (isFuture ? forms(key)[1] : forms(key)[2]); + } + + function special(number) { + return number % 10 === 0 || (number > 10 && number < 20); + } + + function forms(key) { + return units[key].split("_"); + } + + function translate(number, withoutSuffix, key, isFuture) { + var result = number + " "; + if (number === 1) { + return result + translateSingular(number, withoutSuffix, key[0], isFuture); + } else if (withoutSuffix) { + return result + (special(number) ? forms(key)[1] : forms(key)[0]); + } else { + if (isFuture) { + return result + forms(key)[1]; + } else { + return result + (special(number) ? forms(key)[1] : forms(key)[2]); + } + } + } + + function relativeWeekDay(moment, format) { + var nominative = format.indexOf('dddd HH:mm') === -1, + weekDay = weekDays[moment.day()]; + + return nominative ? weekDay : weekDay.substring(0, weekDay.length - 2) + "į"; + } + + return moment.lang("lt", { + months : "sausio_vasario_kovo_balandžio_gegužės_biržėlio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio".split("_"), + monthsShort : "sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"), + weekdays : relativeWeekDay, + weekdaysShort : "Sek_Pir_Ant_Tre_Ket_Pen_Šeš".split("_"), + weekdaysMin : "S_P_A_T_K_Pn_Š".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "YYYY-MM-DD", + LL : "YYYY [m.] MMMM D [d.]", + LLL : "YYYY [m.] MMMM D [d.], LT [val.]", + LLLL : "YYYY [m.] MMMM D [d.], dddd, LT [val.]", + l : "YYYY-MM-DD", + ll : "YYYY [m.] MMMM D [d.]", + lll : "YYYY [m.] MMMM D [d.], LT [val.]", + llll : "YYYY [m.] MMMM D [d.], ddd, LT [val.]" + }, + calendar : { + sameDay : "[Šiandien] LT", + nextDay : "[Rytoj] LT", + nextWeek : "dddd LT", + lastDay : "[Vakar] LT", + lastWeek : "[Praėjusį] dddd LT", + sameElse : "L" + }, + relativeTime : { + future : "po %s", + past : "prieš %s", + s : translateSeconds, + m : translateSingular, + mm : translate, + h : translateSingular, + hh : translate, + d : translateSingular, + dd : translate, + M : translateSingular, + MM : translate, + y : translateSingular, + yy : translate + }, + ordinal : function (number) { + return number + '-oji'; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : latvian (lv) +// author : Kristaps Karlsons : https://github.com/skakri + +(function (factory) { + factory(moment); +}(function (moment) { + var units = { + 'mm': 'minūti_minūtes_minūte_minūtes', + 'hh': 'stundu_stundas_stunda_stundas', + 'dd': 'dienu_dienas_diena_dienas', + 'MM': 'mēnesi_mēnešus_mēnesis_mēneši', + 'yy': 'gadu_gadus_gads_gadi' + }; + + function format(word, number, withoutSuffix) { + var forms = word.split('_'); + if (withoutSuffix) { + return number % 10 === 1 && number !== 11 ? forms[2] : forms[3]; + } else { + return number % 10 === 1 && number !== 11 ? forms[0] : forms[1]; + } + } + + function relativeTimeWithPlural(number, withoutSuffix, key) { + return number + ' ' + format(units[key], number, withoutSuffix); + } + + return moment.lang('lv', { + months : "janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris".split("_"), + monthsShort : "jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec".split("_"), + weekdays : "svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena".split("_"), + weekdaysShort : "Sv_P_O_T_C_Pk_S".split("_"), + weekdaysMin : "Sv_P_O_T_C_Pk_S".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "YYYY. [gada] D. MMMM", + LLL : "YYYY. [gada] D. MMMM, LT", + LLLL : "YYYY. [gada] D. MMMM, dddd, LT" + }, + calendar : { + sameDay : '[Šodien pulksten] LT', + nextDay : '[Rīt pulksten] LT', + nextWeek : 'dddd [pulksten] LT', + lastDay : '[Vakar pulksten] LT', + lastWeek : '[Pagājušā] dddd [pulksten] LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s vēlāk", + past : "%s agrāk", + s : "dažas sekundes", + m : "minūti", + mm : relativeTimeWithPlural, + h : "stundu", + hh : relativeTimeWithPlural, + d : "dienu", + dd : relativeTimeWithPlural, + M : "mēnesi", + MM : relativeTimeWithPlural, + y : "gadu", + yy : relativeTimeWithPlural + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : macedonian (mk) +// author : Borislav Mickov : https://github.com/B0k0 + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('mk', { + months : "јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември".split("_"), + monthsShort : "јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек".split("_"), + weekdays : "недела_понеделник_вторник_среда_четврток_петок_сабота".split("_"), + weekdaysShort : "нед_пон_вто_сре_чет_пет_саб".split("_"), + weekdaysMin : "нe_пo_вт_ср_че_пе_сa".split("_"), + longDateFormat : { + LT : "H:mm", + L : "D.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[Денес во] LT', + nextDay : '[Утре во] LT', + nextWeek : 'dddd [во] LT', + lastDay : '[Вчера во] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[Во изминатата] dddd [во] LT'; + case 1: + case 2: + case 4: + case 5: + return '[Во изминатиот] dddd [во] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : "после %s", + past : "пред %s", + s : "неколку секунди", + m : "минута", + mm : "%d минути", + h : "час", + hh : "%d часа", + d : "ден", + dd : "%d дена", + M : "месец", + MM : "%d месеци", + y : "година", + yy : "%d години" + }, + ordinal : function (number) { + var lastDigit = number % 10, + last2Digits = number % 100; + if (number === 0) { + return number + '-ев'; + } else if (last2Digits === 0) { + return number + '-ен'; + } else if (last2Digits > 10 && last2Digits < 20) { + return number + '-ти'; + } else if (lastDigit === 1) { + return number + '-ви'; + } else if (lastDigit === 2) { + return number + '-ри'; + } else if (lastDigit === 7 || lastDigit === 8) { + return number + '-ми'; + } else { + return number + '-ти'; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : malayalam (ml) +// author : Floyd Pink : https://github.com/floydpink + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('ml', { + months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split("_"), + monthsShort : 'ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.'.split("_"), + weekdays : 'ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച'.split("_"), + weekdaysShort : 'ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി'.split("_"), + weekdaysMin : 'ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ'.split("_"), + longDateFormat : { + LT : "A h:mm -നു", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY, LT", + LLLL : "dddd, D MMMM YYYY, LT" + }, + calendar : { + sameDay : '[ഇന്ന്] LT', + nextDay : '[നാളെ] LT', + nextWeek : 'dddd, LT', + lastDay : '[ഇന്നലെ] LT', + lastWeek : '[കഴിഞ്ഞ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s കഴിഞ്ഞ്", + past : "%s മുൻപ്", + s : "അൽപ നിമിഷങ്ങൾ", + m : "ഒരു മിനിറ്റ്", + mm : "%d മിനിറ്റ്", + h : "ഒരു മണിക്കൂർ", + hh : "%d മണിക്കൂർ", + d : "ഒരു ദിവസം", + dd : "%d ദിവസം", + M : "ഒരു മാസം", + MM : "%d മാസം", + y : "ഒരു വർഷം", + yy : "%d വർഷം" + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return "രാത്രി"; + } else if (hour < 12) { + return "രാവിലെ"; + } else if (hour < 17) { + return "ഉച്ച കഴിഞ്ഞ്"; + } else if (hour < 20) { + return "വൈകുന്നേരം"; + } else { + return "രാത്രി"; + } + } + }); +})); +// moment.js language configuration +// language : Marathi (mr) +// author : Harshad Kale : https://github.com/kalehv + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' + }, + numberMap = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' + }; + + return moment.lang('mr', { + months : 'जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर'.split("_"), + monthsShort: 'जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.'.split("_"), + weekdays : 'रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split("_"), + weekdaysShort : 'रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि'.split("_"), + weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split("_"), + longDateFormat : { + LT : "A h:mm वाजता", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY, LT", + LLLL : "dddd, D MMMM YYYY, LT" + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[उद्या] LT', + nextWeek : 'dddd, LT', + lastDay : '[काल] LT', + lastWeek: '[मागील] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s नंतर", + past : "%s पूर्वी", + s : "सेकंद", + m: "एक मिनिट", + mm: "%d मिनिटे", + h : "एक तास", + hh : "%d तास", + d : "एक दिवस", + dd : "%d दिवस", + M : "एक महिना", + MM : "%d महिने", + y : "एक वर्ष", + yy : "%d वर्षे" + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiem: function (hour, minute, isLower) + { + if (hour < 4) { + return "रात्री"; + } else if (hour < 10) { + return "सकाळी"; + } else if (hour < 17) { + return "दुपारी"; + } else if (hour < 20) { + return "सायंकाळी"; + } else { + return "रात्री"; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Bahasa Malaysia (ms-MY) +// author : Weldan Jamili : https://github.com/weldan + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('ms-my', { + months : "Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"), + monthsShort : "Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"), + weekdays : "Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"), + weekdaysShort : "Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"), + weekdaysMin : "Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"), + longDateFormat : { + LT : "HH.mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY [pukul] LT", + LLLL : "dddd, D MMMM YYYY [pukul] LT" + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'tengahari'; + } else if (hours < 19) { + return 'petang'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Esok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kelmarin pukul] LT', + lastWeek : 'dddd [lepas pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : "dalam %s", + past : "%s yang lepas", + s : "beberapa saat", + m : "seminit", + mm : "%d minit", + h : "sejam", + hh : "%d jam", + d : "sehari", + dd : "%d hari", + M : "sebulan", + MM : "%d bulan", + y : "setahun", + yy : "%d tahun" + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : norwegian bokmål (nb) +// authors : Espen Hovlandsdal : https://github.com/rexxars +// Sigurd Gartmann : https://github.com/sigurdga + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('nb', { + months : "januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"), + monthsShort : "jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.".split("_"), + weekdays : "søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"), + weekdaysShort : "sø._ma._ti._on._to._fr._lø.".split("_"), + weekdaysMin : "sø_ma_ti_on_to_fr_lø".split("_"), + longDateFormat : { + LT : "H.mm", + L : "DD.MM.YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY [kl.] LT", + LLLL : "dddd D. MMMM YYYY [kl.] LT" + }, + calendar : { + sameDay: '[i dag kl.] LT', + nextDay: '[i morgen kl.] LT', + nextWeek: 'dddd [kl.] LT', + lastDay: '[i går kl.] LT', + lastWeek: '[forrige] dddd [kl.] LT', + sameElse: 'L' + }, + relativeTime : { + future : "om %s", + past : "for %s siden", + s : "noen sekunder", + m : "ett minutt", + mm : "%d minutter", + h : "en time", + hh : "%d timer", + d : "en dag", + dd : "%d dager", + M : "en måned", + MM : "%d måneder", + y : "ett år", + yy : "%d år" + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : nepali/nepalese +// author : suvash : https://github.com/suvash + +(function (factory) { + factory(moment); +}(function (moment) { + var symbolMap = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' + }, + numberMap = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' + }; + + return moment.lang('ne', { + months : 'जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर'.split("_"), + monthsShort : 'जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.'.split("_"), + weekdays : 'आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार'.split("_"), + weekdaysShort : 'आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.'.split("_"), + weekdaysMin : 'आइ._सो._मङ्_बु._बि._शु._श.'.split("_"), + longDateFormat : { + LT : "Aको h:mm बजे", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY, LT", + LLLL : "dddd, D MMMM YYYY, LT" + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 3) { + return "राती"; + } else if (hour < 10) { + return "बिहान"; + } else if (hour < 15) { + return "दिउँसो"; + } else if (hour < 18) { + return "बेलुका"; + } else if (hour < 20) { + return "साँझ"; + } else { + return "राती"; + } + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[भोली] LT', + nextWeek : '[आउँदो] dddd[,] LT', + lastDay : '[हिजो] LT', + lastWeek : '[गएको] dddd[,] LT', + sameElse : 'L' + }, + relativeTime : { + future : "%sमा", + past : "%s अगाडी", + s : "केही समय", + m : "एक मिनेट", + mm : "%d मिनेट", + h : "एक घण्टा", + hh : "%d घण्टा", + d : "एक दिन", + dd : "%d दिन", + M : "एक महिना", + MM : "%d महिना", + y : "एक बर्ष", + yy : "%d बर्ष" + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : dutch (nl) +// author : Joris Röling : https://github.com/jjupiter + +(function (factory) { + factory(moment); +}(function (moment) { + var monthsShortWithDots = "jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"), + monthsShortWithoutDots = "jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_"); + + return moment.lang('nl', { + months : "januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShortWithoutDots[m.month()]; + } else { + return monthsShortWithDots[m.month()]; + } + }, + weekdays : "zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"), + weekdaysShort : "zo._ma._di._wo._do._vr._za.".split("_"), + weekdaysMin : "Zo_Ma_Di_Wo_Do_Vr_Za".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD-MM-YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: '[vandaag om] LT', + nextDay: '[morgen om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[gisteren om] LT', + lastWeek: '[afgelopen] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : "over %s", + past : "%s geleden", + s : "een paar seconden", + m : "één minuut", + mm : "%d minuten", + h : "één uur", + hh : "%d uur", + d : "één dag", + dd : "%d dagen", + M : "één maand", + MM : "%d maanden", + y : "één jaar", + yy : "%d jaar" + }, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : norwegian nynorsk (nn) +// author : https://github.com/mechuwind + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('nn', { + months : "januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"), + monthsShort : "jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"), + weekdays : "sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag".split("_"), + weekdaysShort : "sun_mån_tys_ons_tor_fre_lau".split("_"), + weekdaysMin : "su_må_ty_on_to_fr_lø".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: '[I dag klokka] LT', + nextDay: '[I morgon klokka] LT', + nextWeek: 'dddd [klokka] LT', + lastDay: '[I går klokka] LT', + lastWeek: '[Føregåande] dddd [klokka] LT', + sameElse: 'L' + }, + relativeTime : { + future : "om %s", + past : "for %s sidan", + s : "nokre sekund", + m : "eit minutt", + mm : "%d minutt", + h : "ein time", + hh : "%d timar", + d : "ein dag", + dd : "%d dagar", + M : "ein månad", + MM : "%d månader", + y : "eit år", + yy : "%d år" + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : polish (pl) +// author : Rafal Hirsz : https://github.com/evoL + +(function (factory) { + factory(moment); +}(function (moment) { + var monthsNominative = "styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"), + monthsSubjective = "stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_"); + + function plural(n) { + return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1); + } + + function translate(number, withoutSuffix, key) { + var result = number + " "; + switch (key) { + case 'm': + return withoutSuffix ? 'minuta' : 'minutę'; + case 'mm': + return result + (plural(number) ? 'minuty' : 'minut'); + case 'h': + return withoutSuffix ? 'godzina' : 'godzinę'; + case 'hh': + return result + (plural(number) ? 'godziny' : 'godzin'); + case 'MM': + return result + (plural(number) ? 'miesiące' : 'miesięcy'); + case 'yy': + return result + (plural(number) ? 'lata' : 'lat'); + } + } + + return moment.lang('pl', { + months : function (momentToFormat, format) { + if (/D MMMM/.test(format)) { + return monthsSubjective[momentToFormat.month()]; + } else { + return monthsNominative[momentToFormat.month()]; + } + }, + monthsShort : "sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"), + weekdays : "niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"), + weekdaysShort : "nie_pon_wt_śr_czw_pt_sb".split("_"), + weekdaysMin : "N_Pn_Wt_Śr_Cz_Pt_So".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay: '[Dziś o] LT', + nextDay: '[Jutro o] LT', + nextWeek: '[W] dddd [o] LT', + lastDay: '[Wczoraj o] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[W zeszłą niedzielę o] LT'; + case 3: + return '[W zeszłą środę o] LT'; + case 6: + return '[W zeszłą sobotę o] LT'; + default: + return '[W zeszły] dddd [o] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : "za %s", + past : "%s temu", + s : "kilka sekund", + m : translate, + mm : translate, + h : translate, + hh : translate, + d : "1 dzień", + dd : '%d dni', + M : "miesiąc", + MM : translate, + y : "rok", + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : brazilian portuguese (pt-br) +// author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('pt-br', { + months : "janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"), + monthsShort : "jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"), + weekdays : "domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"), + weekdaysShort : "dom_seg_ter_qua_qui_sex_sáb".split("_"), + weekdaysMin : "dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D [de] MMMM [de] YYYY", + LLL : "D [de] MMMM [de] YYYY [às] LT", + LLLL : "dddd, D [de] MMMM [de] YYYY [às] LT" + }, + calendar : { + sameDay: '[Hoje às] LT', + nextDay: '[Amanhã às] LT', + nextWeek: 'dddd [às] LT', + lastDay: '[Ontem às] LT', + lastWeek: function () { + return (this.day() === 0 || this.day() === 6) ? + '[Último] dddd [às] LT' : // Saturday + Sunday + '[Última] dddd [às] LT'; // Monday - Friday + }, + sameElse: 'L' + }, + relativeTime : { + future : "em %s", + past : "%s atrás", + s : "segundos", + m : "um minuto", + mm : "%d minutos", + h : "uma hora", + hh : "%d horas", + d : "um dia", + dd : "%d dias", + M : "um mês", + MM : "%d meses", + y : "um ano", + yy : "%d anos" + }, + ordinal : '%dº' + }); +})); +// moment.js language configuration +// language : portuguese (pt) +// author : Jefferson : https://github.com/jalex79 + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('pt', { + months : "janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"), + monthsShort : "jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"), + weekdays : "domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"), + weekdaysShort : "dom_seg_ter_qua_qui_sex_sáb".split("_"), + weekdaysMin : "dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D [de] MMMM [de] YYYY", + LLL : "D [de] MMMM [de] YYYY LT", + LLLL : "dddd, D [de] MMMM [de] YYYY LT" + }, + calendar : { + sameDay: '[Hoje às] LT', + nextDay: '[Amanhã às] LT', + nextWeek: 'dddd [às] LT', + lastDay: '[Ontem às] LT', + lastWeek: function () { + return (this.day() === 0 || this.day() === 6) ? + '[Último] dddd [às] LT' : // Saturday + Sunday + '[Última] dddd [às] LT'; // Monday - Friday + }, + sameElse: 'L' + }, + relativeTime : { + future : "em %s", + past : "há %s", + s : "segundos", + m : "um minuto", + mm : "%d minutos", + h : "uma hora", + hh : "%d horas", + d : "um dia", + dd : "%d dias", + M : "um mês", + MM : "%d meses", + y : "um ano", + yy : "%d anos" + }, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : romanian (ro) +// author : Vlad Gurdiga : https://github.com/gurdiga +// author : Valentin Agachi : https://github.com/avaly + +(function (factory) { + factory(moment); +}(function (moment) { + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'mm': 'minute', + 'hh': 'ore', + 'dd': 'zile', + 'MM': 'luni', + 'yy': 'ani' + }, + separator = ' '; + if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) { + separator = ' de '; + } + + return number + separator + format[key]; + } + + return moment.lang('ro', { + months : "ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie".split("_"), + monthsShort : "ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.".split("_"), + weekdays : "duminică_luni_marți_miercuri_joi_vineri_sâmbătă".split("_"), + weekdaysShort : "Dum_Lun_Mar_Mie_Joi_Vin_Sâm".split("_"), + weekdaysMin : "Du_Lu_Ma_Mi_Jo_Vi_Sâ".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY H:mm", + LLLL : "dddd, D MMMM YYYY H:mm" + }, + calendar : { + sameDay: "[azi la] LT", + nextDay: '[mâine la] LT', + nextWeek: 'dddd [la] LT', + lastDay: '[ieri la] LT', + lastWeek: '[fosta] dddd [la] LT', + sameElse: 'L' + }, + relativeTime : { + future : "peste %s", + past : "%s în urmă", + s : "câteva secunde", + m : "un minut", + mm : relativeTimeWithPlural, + h : "o oră", + hh : relativeTimeWithPlural, + d : "o zi", + dd : relativeTimeWithPlural, + M : "o lună", + MM : relativeTimeWithPlural, + y : "un an", + yy : relativeTimeWithPlural + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : russian (ru) +// author : Viktorminator : https://github.com/Viktorminator +// Author : Menelion Elensúle : https://github.com/Oire + +(function (factory) { + factory(moment); +}(function (moment) { + function plural(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); + } + + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут', + 'hh': 'час_часа_часов', + 'dd': 'день_дня_дней', + 'MM': 'месяц_месяца_месяцев', + 'yy': 'год_года_лет' + }; + if (key === 'm') { + return withoutSuffix ? 'минута' : 'минуту'; + } + else { + return number + ' ' + plural(format[key], +number); + } + } + + function monthsCaseReplace(m, format) { + var months = { + 'nominative': 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'), + 'accusative': 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_') + }, + + nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? + 'accusative' : + 'nominative'; + + return months[nounCase][m.month()]; + } + + function monthsShortCaseReplace(m, format) { + var monthsShort = { + 'nominative': 'янв_фев_мар_апр_май_июнь_июль_авг_сен_окт_ноя_дек'.split('_'), + 'accusative': 'янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек'.split('_') + }, + + nounCase = (/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/).test(format) ? + 'accusative' : + 'nominative'; + + return monthsShort[nounCase][m.month()]; + } + + function weekdaysCaseReplace(m, format) { + var weekdays = { + 'nominative': 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'), + 'accusative': 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_') + }, + + nounCase = (/\[ ?[Вв] ?(?:прошлую|следующую)? ?\] ?dddd/).test(format) ? + 'accusative' : + 'nominative'; + + return weekdays[nounCase][m.day()]; + } + + return moment.lang('ru', { + months : monthsCaseReplace, + monthsShort : monthsShortCaseReplace, + weekdays : weekdaysCaseReplace, + weekdaysShort : "вс_пн_вт_ср_чт_пт_сб".split("_"), + weekdaysMin : "вс_пн_вт_ср_чт_пт_сб".split("_"), + monthsParse : [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[й|я]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i], + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY г.", + LLL : "D MMMM YYYY г., LT", + LLLL : "dddd, D MMMM YYYY г., LT" + }, + calendar : { + sameDay: '[Сегодня в] LT', + nextDay: '[Завтра в] LT', + lastDay: '[Вчера в] LT', + nextWeek: function () { + return this.day() === 2 ? '[Во] dddd [в] LT' : '[В] dddd [в] LT'; + }, + lastWeek: function () { + switch (this.day()) { + case 0: + return '[В прошлое] dddd [в] LT'; + case 1: + case 2: + case 4: + return '[В прошлый] dddd [в] LT'; + case 3: + case 5: + case 6: + return '[В прошлую] dddd [в] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : "через %s", + past : "%s назад", + s : "несколько секунд", + m : relativeTimeWithPlural, + mm : relativeTimeWithPlural, + h : "час", + hh : relativeTimeWithPlural, + d : "день", + dd : relativeTimeWithPlural, + M : "месяц", + MM : relativeTimeWithPlural, + y : "год", + yy : relativeTimeWithPlural + }, + + meridiemParse: /ночи|утра|дня|вечера/i, + isPM : function (input) { + return /^(дня|вечера)$/.test(input); + }, + + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return "ночи"; + } else if (hour < 12) { + return "утра"; + } else if (hour < 17) { + return "дня"; + } else { + return "вечера"; + } + }, + + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + return number + '-й'; + case 'D': + return number + '-го'; + case 'w': + case 'W': + return number + '-я'; + default: + return number; + } + }, + + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : slovak (sk) +// author : Martin Minka : https://github.com/k2s +// based on work of petrbela : https://github.com/petrbela + +(function (factory) { + factory(moment); +}(function (moment) { + var months = "január_február_marec_apríl_máj_jún_júl_august_september_október_november_december".split("_"), + monthsShort = "jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec".split("_"); + + function plural(n) { + return (n > 1) && (n < 5); + } + + function translate(number, withoutSuffix, key, isFuture) { + var result = number + " "; + switch (key) { + case 's': // a few seconds / in a few seconds / a few seconds ago + return (withoutSuffix || isFuture) ? 'pár sekúnd' : 'pár sekundami'; + case 'm': // a minute / in a minute / a minute ago + return withoutSuffix ? 'minúta' : (isFuture ? 'minútu' : 'minútou'); + case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'minúty' : 'minút'); + } else { + return result + 'minútami'; + } + break; + case 'h': // an hour / in an hour / an hour ago + return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); + case 'hh': // 9 hours / in 9 hours / 9 hours ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'hodiny' : 'hodín'); + } else { + return result + 'hodinami'; + } + break; + case 'd': // a day / in a day / a day ago + return (withoutSuffix || isFuture) ? 'deň' : 'dňom'; + case 'dd': // 9 days / in 9 days / 9 days ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'dni' : 'dní'); + } else { + return result + 'dňami'; + } + break; + case 'M': // a month / in a month / a month ago + return (withoutSuffix || isFuture) ? 'mesiac' : 'mesiacom'; + case 'MM': // 9 months / in 9 months / 9 months ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'mesiace' : 'mesiacov'); + } else { + return result + 'mesiacmi'; + } + break; + case 'y': // a year / in a year / a year ago + return (withoutSuffix || isFuture) ? 'rok' : 'rokom'; + case 'yy': // 9 years / in 9 years / 9 years ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'roky' : 'rokov'); + } else { + return result + 'rokmi'; + } + break; + } + } + + return moment.lang('sk', { + months : months, + monthsShort : monthsShort, + monthsParse : (function (months, monthsShort) { + var i, _monthsParse = []; + for (i = 0; i < 12; i++) { + // use custom parser to solve problem with July (červenec) + _monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i'); + } + return _monthsParse; + }(months, monthsShort)), + weekdays : "nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota".split("_"), + weekdaysShort : "ne_po_ut_st_št_pi_so".split("_"), + weekdaysMin : "ne_po_ut_st_št_pi_so".split("_"), + longDateFormat : { + LT: "H:mm", + L : "DD.MM.YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd D. MMMM YYYY LT" + }, + calendar : { + sameDay: "[dnes o] LT", + nextDay: '[zajtra o] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[v nedeľu o] LT'; + case 1: + case 2: + return '[v] dddd [o] LT'; + case 3: + return '[v stredu o] LT'; + case 4: + return '[vo štvrtok o] LT'; + case 5: + return '[v piatok o] LT'; + case 6: + return '[v sobotu o] LT'; + } + }, + lastDay: '[včera o] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[minulú nedeľu o] LT'; + case 1: + case 2: + return '[minulý] dddd [o] LT'; + case 3: + return '[minulú stredu o] LT'; + case 4: + case 5: + return '[minulý] dddd [o] LT'; + case 6: + return '[minulú sobotu o] LT'; + } + }, + sameElse: "L" + }, + relativeTime : { + future : "za %s", + past : "pred %s", + s : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : slovenian (sl) +// author : Robert Sedovšek : https://github.com/sedovsek + +(function (factory) { + factory(moment); +}(function (moment) { + function translate(number, withoutSuffix, key) { + var result = number + " "; + switch (key) { + case 'm': + return withoutSuffix ? 'ena minuta' : 'eno minuto'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2) { + result += 'minuti'; + } else if (number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minut'; + } + return result; + case 'h': + return withoutSuffix ? 'ena ura' : 'eno uro'; + case 'hh': + if (number === 1) { + result += 'ura'; + } else if (number === 2) { + result += 'uri'; + } else if (number === 3 || number === 4) { + result += 'ure'; + } else { + result += 'ur'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dni'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mesec'; + } else if (number === 2) { + result += 'meseca'; + } else if (number === 3 || number === 4) { + result += 'mesece'; + } else { + result += 'mesecev'; + } + return result; + case 'yy': + if (number === 1) { + result += 'leto'; + } else if (number === 2) { + result += 'leti'; + } else if (number === 3 || number === 4) { + result += 'leta'; + } else { + result += 'let'; + } + return result; + } + } + + return moment.lang('sl', { + months : "januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"), + monthsShort : "jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"), + weekdays : "nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota".split("_"), + weekdaysShort : "ned._pon._tor._sre._čet._pet._sob.".split("_"), + weekdaysMin : "ne_po_to_sr_če_pe_so".split("_"), + longDateFormat : { + LT : "H:mm", + L : "DD. MM. YYYY", + LL : "D. MMMM YYYY", + LLL : "D. MMMM YYYY LT", + LLLL : "dddd, D. MMMM YYYY LT" + }, + calendar : { + sameDay : '[danes ob] LT', + nextDay : '[jutri ob] LT', + + nextWeek : function () { + switch (this.day()) { + case 0: + return '[v] [nedeljo] [ob] LT'; + case 3: + return '[v] [sredo] [ob] LT'; + case 6: + return '[v] [soboto] [ob] LT'; + case 1: + case 2: + case 4: + case 5: + return '[v] dddd [ob] LT'; + } + }, + lastDay : '[včeraj ob] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[prejšnja] dddd [ob] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prejšnji] dddd [ob] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : "čez %s", + past : "%s nazaj", + s : "nekaj sekund", + m : translate, + mm : translate, + h : translate, + hh : translate, + d : "en dan", + dd : translate, + M : "en mesec", + MM : translate, + y : "eno leto", + yy : translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Albanian (sq) +// author : Flakërim Ismani : https://github.com/flakerimi +// author: Menelion Elensúle: https://github.com/Oire (tests) +// author : Oerd Cukalla : https://github.com/oerd (fixes) + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('sq', { + months : "Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor".split("_"), + monthsShort : "Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj".split("_"), + weekdays : "E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë".split("_"), + weekdaysShort : "Die_Hën_Mar_Mër_Enj_Pre_Sht".split("_"), + weekdaysMin : "D_H_Ma_Më_E_P_Sh".split("_"), + meridiem : function (hours, minutes, isLower) { + return hours < 12 ? 'PD' : 'MD'; + }, + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[Sot në] LT', + nextDay : '[Nesër në] LT', + nextWeek : 'dddd [në] LT', + lastDay : '[Dje në] LT', + lastWeek : 'dddd [e kaluar në] LT', + sameElse : 'L' + }, + relativeTime : { + future : "në %s", + past : "%s më parë", + s : "disa sekonda", + m : "një minutë", + mm : "%d minuta", + h : "një orë", + hh : "%d orë", + d : "një ditë", + dd : "%d ditë", + M : "një muaj", + MM : "%d muaj", + y : "një vit", + yy : "%d vite" + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Serbian-cyrillic (sr-cyrl) +// author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j + +(function (factory) { + factory(moment); +}(function (moment) { + + var translator = { + words: { //Different grammatical cases + m: ['један минут', 'једне минуте'], + mm: ['минут', 'минуте', 'минута'], + h: ['један сат', 'једног сата'], + hh: ['сат', 'сата', 'сати'], + dd: ['дан', 'дана', 'дана'], + MM: ['месец', 'месеца', 'месеци'], + yy: ['година', 'године', 'година'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator.correctGrammaticalCase(number, wordKey); + } + } + }; + + return moment.lang('sr-cyrl', { + months: ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + monthsShort: ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + weekdays: ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + weekdaysShort: ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + weekdaysMin: ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + longDateFormat: { + LT: "H:mm", + L: "DD. MM. YYYY", + LL: "D. MMMM YYYY", + LLL: "D. MMMM YYYY LT", + LLLL: "dddd, D. MMMM YYYY LT" + }, + calendar: { + sameDay: '[данас у] LT', + nextDay: '[сутра у] LT', + + nextWeek: function () { + switch (this.day()) { + case 0: + return '[у] [недељу] [у] LT'; + case 3: + return '[у] [среду] [у] LT'; + case 6: + return '[у] [суботу] [у] LT'; + case 1: + case 2: + case 4: + case 5: + return '[у] dddd [у] LT'; + } + }, + lastDay : '[јуче у] LT', + lastWeek : function () { + var lastWeekDays = [ + '[прошле] [недеље] [у] LT', + '[прошлог] [понедељка] [у] LT', + '[прошлог] [уторка] [у] LT', + '[прошле] [среде] [у] LT', + '[прошлог] [четвртка] [у] LT', + '[прошлог] [петка] [у] LT', + '[прошле] [суботе] [у] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : "за %s", + past : "пре %s", + s : "неколико секунди", + m : translator.translate, + mm : translator.translate, + h : translator.translate, + hh : translator.translate, + d : "дан", + dd : translator.translate, + M : "месец", + MM : translator.translate, + y : "годину", + yy : translator.translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Serbian-latin (sr) +// author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j + +(function (factory) { + factory(moment); +}(function (moment) { + + var translator = { + words: { //Different grammatical cases + m: ['jedan minut', 'jedne minute'], + mm: ['minut', 'minute', 'minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['sat', 'sata', 'sati'], + dd: ['dan', 'dana', 'dana'], + MM: ['mesec', 'meseca', 'meseci'], + yy: ['godina', 'godine', 'godina'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator.correctGrammaticalCase(number, wordKey); + } + } + }; + + return moment.lang('sr', { + months: ['januar', 'februar', 'mart', 'april', 'maj', 'jun', 'jul', 'avgust', 'septembar', 'oktobar', 'novembar', 'decembar'], + monthsShort: ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun', 'jul', 'avg.', 'sep.', 'okt.', 'nov.', 'dec.'], + weekdays: ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], + weekdaysShort: ['ned.', 'pon.', 'uto.', 'sre.', 'čet.', 'pet.', 'sub.'], + weekdaysMin: ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + longDateFormat: { + LT: "H:mm", + L: "DD. MM. YYYY", + LL: "D. MMMM YYYY", + LLL: "D. MMMM YYYY LT", + LLLL: "dddd, D. MMMM YYYY LT" + }, + calendar: { + sameDay: '[danas u] LT', + nextDay: '[sutra u] LT', + + nextWeek: function () { + switch (this.day()) { + case 0: + return '[u] [nedelju] [u] LT'; + case 3: + return '[u] [sredu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[juče u] LT', + lastWeek : function () { + var lastWeekDays = [ + '[prošle] [nedelje] [u] LT', + '[prošlog] [ponedeljka] [u] LT', + '[prošlog] [utorka] [u] LT', + '[prošle] [srede] [u] LT', + '[prošlog] [četvrtka] [u] LT', + '[prošlog] [petka] [u] LT', + '[prošle] [subote] [u] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : "za %s", + past : "pre %s", + s : "nekoliko sekundi", + m : translator.translate, + mm : translator.translate, + h : translator.translate, + hh : translator.translate, + d : "dan", + dd : translator.translate, + M : "mesec", + MM : translator.translate, + y : "godinu", + yy : translator.translate + }, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : swedish (sv) +// author : Jens Alm : https://github.com/ulmus + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('sv', { + months : "januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"), + monthsShort : "jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"), + weekdays : "söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"), + weekdaysShort : "sön_mån_tis_ons_tor_fre_lör".split("_"), + weekdaysMin : "sö_må_ti_on_to_fr_lö".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "YYYY-MM-DD", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: '[Idag] LT', + nextDay: '[Imorgon] LT', + lastDay: '[Igår] LT', + nextWeek: 'dddd LT', + lastWeek: '[Förra] dddd[en] LT', + sameElse: 'L' + }, + relativeTime : { + future : "om %s", + past : "för %s sedan", + s : "några sekunder", + m : "en minut", + mm : "%d minuter", + h : "en timme", + hh : "%d timmar", + d : "en dag", + dd : "%d dagar", + M : "en månad", + MM : "%d månader", + y : "ett år", + yy : "%d år" + }, + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'e' : + (b === 1) ? 'a' : + (b === 2) ? 'a' : + (b === 3) ? 'e' : 'e'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : tamil (ta) +// author : Arjunkumar Krishnamoorthy : https://github.com/tk120404 + +(function (factory) { + factory(moment); +}(function (moment) { + /*var symbolMap = { + '1': '௧', + '2': '௨', + '3': '௩', + '4': '௪', + '5': '௫', + '6': '௬', + '7': '௭', + '8': '௮', + '9': '௯', + '0': '௦' + }, + numberMap = { + '௧': '1', + '௨': '2', + '௩': '3', + '௪': '4', + '௫': '5', + '௬': '6', + '௭': '7', + '௮': '8', + '௯': '9', + '௦': '0' + }; */ + + return moment.lang('ta', { + months : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split("_"), + monthsShort : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split("_"), + weekdays : 'ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை'.split("_"), + weekdaysShort : 'ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி'.split("_"), + weekdaysMin : 'ஞா_தி_செ_பு_வி_வெ_ச'.split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY, LT", + LLLL : "dddd, D MMMM YYYY, LT" + }, + calendar : { + sameDay : '[இன்று] LT', + nextDay : '[நாளை] LT', + nextWeek : 'dddd, LT', + lastDay : '[நேற்று] LT', + lastWeek : '[கடந்த வாரம்] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s இல்", + past : "%s முன்", + s : "ஒரு சில விநாடிகள்", + m : "ஒரு நிமிடம்", + mm : "%d நிமிடங்கள்", + h : "ஒரு மணி நேரம்", + hh : "%d மணி நேரம்", + d : "ஒரு நாள்", + dd : "%d நாட்கள்", + M : "ஒரு மாதம்", + MM : "%d மாதங்கள்", + y : "ஒரு வருடம்", + yy : "%d ஆண்டுகள்" + }, +/* preparse: function (string) { + return string.replace(/[௧௨௩௪௫௬௭௮௯௦]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + },*/ + ordinal : function (number) { + return number + 'வது'; + }, + + +// refer http://ta.wikipedia.org/s/1er1 + + meridiem : function (hour, minute, isLower) { + if (hour >= 6 && hour <= 10) { + return " காலை"; + } else if (hour >= 10 && hour <= 14) { + return " நண்பகல்"; + } else if (hour >= 14 && hour <= 18) { + return " எற்பாடு"; + } else if (hour >= 18 && hour <= 20) { + return " மாலை"; + } else if (hour >= 20 && hour <= 24) { + return " இரவு"; + } else if (hour >= 0 && hour <= 6) { + return " வைகறை"; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : thai (th) +// author : Kridsada Thanabulpong : https://github.com/sirn + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('th', { + months : "มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"), + monthsShort : "มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"), + weekdays : "อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"), + weekdaysShort : "อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"), // yes, three characters difference + weekdaysMin : "อา._จ._อ._พ._พฤ._ศ._ส.".split("_"), + longDateFormat : { + LT : "H นาฬิกา m นาที", + L : "YYYY/MM/DD", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY เวลา LT", + LLLL : "วันddddที่ D MMMM YYYY เวลา LT" + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return "ก่อนเที่ยง"; + } else { + return "หลังเที่ยง"; + } + }, + calendar : { + sameDay : '[วันนี้ เวลา] LT', + nextDay : '[พรุ่งนี้ เวลา] LT', + nextWeek : 'dddd[หน้า เวลา] LT', + lastDay : '[เมื่อวานนี้ เวลา] LT', + lastWeek : '[วัน]dddd[ที่แล้ว เวลา] LT', + sameElse : 'L' + }, + relativeTime : { + future : "อีก %s", + past : "%sที่แล้ว", + s : "ไม่กี่วินาที", + m : "1 นาที", + mm : "%d นาที", + h : "1 ชั่วโมง", + hh : "%d ชั่วโมง", + d : "1 วัน", + dd : "%d วัน", + M : "1 เดือน", + MM : "%d เดือน", + y : "1 ปี", + yy : "%d ปี" + } + }); +})); +// moment.js language configuration +// language : Tagalog/Filipino (tl-ph) +// author : Dan Hagman + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('tl-ph', { + months : "Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"), + monthsShort : "Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"), + weekdays : "Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"), + weekdaysShort : "Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"), + weekdaysMin : "Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "MM/D/YYYY", + LL : "MMMM D, YYYY", + LLL : "MMMM D, YYYY LT", + LLLL : "dddd, MMMM DD, YYYY LT" + }, + calendar : { + sameDay: "[Ngayon sa] LT", + nextDay: '[Bukas sa] LT', + nextWeek: 'dddd [sa] LT', + lastDay: '[Kahapon sa] LT', + lastWeek: 'dddd [huling linggo] LT', + sameElse: 'L' + }, + relativeTime : { + future : "sa loob ng %s", + past : "%s ang nakalipas", + s : "ilang segundo", + m : "isang minuto", + mm : "%d minuto", + h : "isang oras", + hh : "%d oras", + d : "isang araw", + dd : "%d araw", + M : "isang buwan", + MM : "%d buwan", + y : "isang taon", + yy : "%d taon" + }, + ordinal : function (number) { + return number; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : turkish (tr) +// authors : Erhan Gundogan : https://github.com/erhangundogan, +// Burak Yiğit Kaya: https://github.com/BYK + +(function (factory) { + factory(moment); +}(function (moment) { + + var suffixes = { + 1: "'inci", + 5: "'inci", + 8: "'inci", + 70: "'inci", + 80: "'inci", + + 2: "'nci", + 7: "'nci", + 20: "'nci", + 50: "'nci", + + 3: "'üncü", + 4: "'üncü", + 100: "'üncü", + + 6: "'ncı", + + 9: "'uncu", + 10: "'uncu", + 30: "'uncu", + + 60: "'ıncı", + 90: "'ıncı" + }; + + return moment.lang('tr', { + months : "Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"), + monthsShort : "Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"), + weekdays : "Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"), + weekdaysShort : "Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"), + weekdaysMin : "Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd, D MMMM YYYY LT" + }, + calendar : { + sameDay : '[bugün saat] LT', + nextDay : '[yarın saat] LT', + nextWeek : '[haftaya] dddd [saat] LT', + lastDay : '[dün] LT', + lastWeek : '[geçen hafta] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : "%s sonra", + past : "%s önce", + s : "birkaç saniye", + m : "bir dakika", + mm : "%d dakika", + h : "bir saat", + hh : "%d saat", + d : "bir gün", + dd : "%d gün", + M : "bir ay", + MM : "%d ay", + y : "bir yıl", + yy : "%d yıl" + }, + ordinal : function (number) { + if (number === 0) { // special case for zero + return number + "'ıncı"; + } + var a = number % 10, + b = number % 100 - a, + c = number >= 100 ? 100 : null; + + return number + (suffixes[a] || suffixes[b] || suffixes[c]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Morocco Central Atlas Tamaziɣt in Latin (tzm-latn) +// author : Abdel Said : https://github.com/abdelsaid + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('tzm-latn', { + months : "innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir".split("_"), + monthsShort : "innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir".split("_"), + weekdays : "asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"), + weekdaysShort : "asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"), + weekdaysMin : "asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: "[asdkh g] LT", + nextDay: '[aska g] LT', + nextWeek: 'dddd [g] LT', + lastDay: '[assant g] LT', + lastWeek: 'dddd [g] LT', + sameElse: 'L' + }, + relativeTime : { + future : "dadkh s yan %s", + past : "yan %s", + s : "imik", + m : "minuḍ", + mm : "%d minuḍ", + h : "saɛa", + hh : "%d tassaɛin", + d : "ass", + dd : "%d ossan", + M : "ayowr", + MM : "%d iyyirn", + y : "asgas", + yy : "%d isgasn" + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : Morocco Central Atlas Tamaziɣt (tzm) +// author : Abdel Said : https://github.com/abdelsaid + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('tzm', { + months : "ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ".split("_"), + monthsShort : "ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ".split("_"), + weekdays : "ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"), + weekdaysShort : "ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"), + weekdaysMin : "ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "dddd D MMMM YYYY LT" + }, + calendar : { + sameDay: "[ⴰⵙⴷⵅ ⴴ] LT", + nextDay: '[ⴰⵙⴽⴰ ⴴ] LT', + nextWeek: 'dddd [ⴴ] LT', + lastDay: '[ⴰⵚⴰⵏⵜ ⴴ] LT', + lastWeek: 'dddd [ⴴ] LT', + sameElse: 'L' + }, + relativeTime : { + future : "ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s", + past : "ⵢⴰⵏ %s", + s : "ⵉⵎⵉⴽ", + m : "ⵎⵉⵏⵓⴺ", + mm : "%d ⵎⵉⵏⵓⴺ", + h : "ⵙⴰⵄⴰ", + hh : "%d ⵜⴰⵙⵙⴰⵄⵉⵏ", + d : "ⴰⵙⵙ", + dd : "%d oⵙⵙⴰⵏ", + M : "ⴰⵢoⵓⵔ", + MM : "%d ⵉⵢⵢⵉⵔⵏ", + y : "ⴰⵙⴳⴰⵙ", + yy : "%d ⵉⵙⴳⴰⵙⵏ" + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : ukrainian (uk) +// author : zemlanin : https://github.com/zemlanin +// Author : Menelion Elensúle : https://github.com/Oire + +(function (factory) { + factory(moment); +}(function (moment) { + function plural(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); + } + + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'mm': 'хвилина_хвилини_хвилин', + 'hh': 'година_години_годин', + 'dd': 'день_дні_днів', + 'MM': 'місяць_місяці_місяців', + 'yy': 'рік_роки_років' + }; + if (key === 'm') { + return withoutSuffix ? 'хвилина' : 'хвилину'; + } + else if (key === 'h') { + return withoutSuffix ? 'година' : 'годину'; + } + else { + return number + ' ' + plural(format[key], +number); + } + } + + function monthsCaseReplace(m, format) { + var months = { + 'nominative': 'січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень'.split('_'), + 'accusative': 'січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня'.split('_') + }, + + nounCase = (/D[oD]? *MMMM?/).test(format) ? + 'accusative' : + 'nominative'; + + return months[nounCase][m.month()]; + } + + function weekdaysCaseReplace(m, format) { + var weekdays = { + 'nominative': 'неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота'.split('_'), + 'accusative': 'неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу'.split('_'), + 'genitive': 'неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи'.split('_') + }, + + nounCase = (/(\[[ВвУу]\]) ?dddd/).test(format) ? + 'accusative' : + ((/\[?(?:минулої|наступної)? ?\] ?dddd/).test(format) ? + 'genitive' : + 'nominative'); + + return weekdays[nounCase][m.day()]; + } + + function processHoursFunction(str) { + return function () { + return str + 'о' + (this.hours() === 11 ? 'б' : '') + '] LT'; + }; + } + + return moment.lang('uk', { + months : monthsCaseReplace, + monthsShort : "січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд".split("_"), + weekdays : weekdaysCaseReplace, + weekdaysShort : "нд_пн_вт_ср_чт_пт_сб".split("_"), + weekdaysMin : "нд_пн_вт_ср_чт_пт_сб".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD.MM.YYYY", + LL : "D MMMM YYYY р.", + LLL : "D MMMM YYYY р., LT", + LLLL : "dddd, D MMMM YYYY р., LT" + }, + calendar : { + sameDay: processHoursFunction('[Сьогодні '), + nextDay: processHoursFunction('[Завтра '), + lastDay: processHoursFunction('[Вчора '), + nextWeek: processHoursFunction('[У] dddd ['), + lastWeek: function () { + switch (this.day()) { + case 0: + case 3: + case 5: + case 6: + return processHoursFunction('[Минулої] dddd [').call(this); + case 1: + case 2: + case 4: + return processHoursFunction('[Минулого] dddd [').call(this); + } + }, + sameElse: 'L' + }, + relativeTime : { + future : "за %s", + past : "%s тому", + s : "декілька секунд", + m : relativeTimeWithPlural, + mm : relativeTimeWithPlural, + h : "годину", + hh : relativeTimeWithPlural, + d : "день", + dd : relativeTimeWithPlural, + M : "місяць", + MM : relativeTimeWithPlural, + y : "рік", + yy : relativeTimeWithPlural + }, + + // M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason + + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return "ночі"; + } else if (hour < 12) { + return "ранку"; + } else if (hour < 17) { + return "дня"; + } else { + return "вечора"; + } + }, + + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return number + '-й'; + case 'D': + return number + '-го'; + default: + return number; + } + }, + + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : uzbek +// author : Sardor Muminov : https://github.com/muminoff + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('uz', { + months : "январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"), + monthsShort : "янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек".split("_"), + weekdays : "Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба".split("_"), + weekdaysShort : "Якш_Душ_Сеш_Чор_Пай_Жум_Шан".split("_"), + weekdaysMin : "Як_Ду_Се_Чо_Па_Жу_Ша".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM YYYY", + LLL : "D MMMM YYYY LT", + LLLL : "D MMMM YYYY, dddd LT" + }, + calendar : { + sameDay : '[Бугун соат] LT [да]', + nextDay : '[Эртага] LT [да]', + nextWeek : 'dddd [куни соат] LT [да]', + lastDay : '[Кеча соат] LT [да]', + lastWeek : '[Утган] dddd [куни соат] LT [да]', + sameElse : 'L' + }, + relativeTime : { + future : "Якин %s ичида", + past : "Бир неча %s олдин", + s : "фурсат", + m : "бир дакика", + mm : "%d дакика", + h : "бир соат", + hh : "%d соат", + d : "бир кун", + dd : "%d кун", + M : "бир ой", + MM : "%d ой", + y : "бир йил", + yy : "%d йил" + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : vietnamese (vi) +// author : Bang Nguyen : https://github.com/bangnk + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('vi', { + months : "tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12".split("_"), + monthsShort : "Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12".split("_"), + weekdays : "chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy".split("_"), + weekdaysShort : "CN_T2_T3_T4_T5_T6_T7".split("_"), + weekdaysMin : "CN_T2_T3_T4_T5_T6_T7".split("_"), + longDateFormat : { + LT : "HH:mm", + L : "DD/MM/YYYY", + LL : "D MMMM [năm] YYYY", + LLL : "D MMMM [năm] YYYY LT", + LLLL : "dddd, D MMMM [năm] YYYY LT", + l : "DD/M/YYYY", + ll : "D MMM YYYY", + lll : "D MMM YYYY LT", + llll : "ddd, D MMM YYYY LT" + }, + calendar : { + sameDay: "[Hôm nay lúc] LT", + nextDay: '[Ngày mai lúc] LT', + nextWeek: 'dddd [tuần tới lúc] LT', + lastDay: '[Hôm qua lúc] LT', + lastWeek: 'dddd [tuần rồi lúc] LT', + sameElse: 'L' + }, + relativeTime : { + future : "%s tới", + past : "%s trước", + s : "vài giây", + m : "một phút", + mm : "%d phút", + h : "một giờ", + hh : "%d giờ", + d : "một ngày", + dd : "%d ngày", + M : "một tháng", + MM : "%d tháng", + y : "một năm", + yy : "%d năm" + }, + ordinal : function (number) { + return number; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : chinese +// author : suupic : https://github.com/suupic +// author : Zeno Zeng : https://github.com/zenozeng + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('zh-cn', { + months : "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"), + monthsShort : "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"), + weekdays : "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"), + weekdaysShort : "周日_周一_周二_周三_周四_周五_周六".split("_"), + weekdaysMin : "日_一_二_三_四_五_六".split("_"), + longDateFormat : { + LT : "Ah点mm", + L : "YYYY-MM-DD", + LL : "YYYY年MMMD日", + LLL : "YYYY年MMMD日LT", + LLLL : "YYYY年MMMD日ddddLT", + l : "YYYY-MM-DD", + ll : "YYYY年MMMD日", + lll : "YYYY年MMMD日LT", + llll : "YYYY年MMMD日ddddLT" + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return "凌晨"; + } else if (hm < 900) { + return "早上"; + } else if (hm < 1130) { + return "上午"; + } else if (hm < 1230) { + return "中午"; + } else if (hm < 1800) { + return "下午"; + } else { + return "晚上"; + } + }, + calendar : { + sameDay : function () { + return this.minutes() === 0 ? "[今天]Ah[点整]" : "[今天]LT"; + }, + nextDay : function () { + return this.minutes() === 0 ? "[明天]Ah[点整]" : "[明天]LT"; + }, + lastDay : function () { + return this.minutes() === 0 ? "[昨天]Ah[点整]" : "[昨天]LT"; + }, + nextWeek : function () { + var startOfWeek, prefix; + startOfWeek = moment().startOf('week'); + prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]'; + return this.minutes() === 0 ? prefix + "dddAh点整" : prefix + "dddAh点mm"; + }, + lastWeek : function () { + var startOfWeek, prefix; + startOfWeek = moment().startOf('week'); + prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'; + return this.minutes() === 0 ? prefix + "dddAh点整" : prefix + "dddAh点mm"; + }, + sameElse : 'LL' + }, + ordinal : function (number, period) { + switch (period) { + case "d": + case "D": + case "DDD": + return number + "日"; + case "M": + return number + "月"; + case "w": + case "W": + return number + "周"; + default: + return number; + } + }, + relativeTime : { + future : "%s内", + past : "%s前", + s : "几秒", + m : "1分钟", + mm : "%d分钟", + h : "1小时", + hh : "%d小时", + d : "1天", + dd : "%d天", + M : "1个月", + MM : "%d个月", + y : "1年", + yy : "%d年" + }, + week : { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); +})); +// moment.js language configuration +// language : traditional chinese (zh-tw) +// author : Ben : https://github.com/ben-lin + +(function (factory) { + factory(moment); +}(function (moment) { + return moment.lang('zh-tw', { + months : "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"), + monthsShort : "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"), + weekdays : "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"), + weekdaysShort : "週日_週一_週二_週三_週四_週五_週六".split("_"), + weekdaysMin : "日_一_二_三_四_五_六".split("_"), + longDateFormat : { + LT : "Ah點mm", + L : "YYYY年MMMD日", + LL : "YYYY年MMMD日", + LLL : "YYYY年MMMD日LT", + LLLL : "YYYY年MMMD日ddddLT", + l : "YYYY年MMMD日", + ll : "YYYY年MMMD日", + lll : "YYYY年MMMD日LT", + llll : "YYYY年MMMD日ddddLT" + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 900) { + return "早上"; + } else if (hm < 1130) { + return "上午"; + } else if (hm < 1230) { + return "中午"; + } else if (hm < 1800) { + return "下午"; + } else { + return "晚上"; + } + }, + calendar : { + sameDay : '[今天]LT', + nextDay : '[明天]LT', + nextWeek : '[下]ddddLT', + lastDay : '[昨天]LT', + lastWeek : '[上]ddddLT', + sameElse : 'L' + }, + ordinal : function (number, period) { + switch (period) { + case "d" : + case "D" : + case "DDD" : + return number + "日"; + case "M" : + return number + "月"; + case "w" : + case "W" : + return number + "週"; + default : + return number; + } + }, + relativeTime : { + future : "%s內", + past : "%s前", + s : "幾秒", + m : "一分鐘", + mm : "%d分鐘", + h : "一小時", + hh : "%d小時", + d : "一天", + dd : "%d天", + M : "一個月", + MM : "%d個月", + y : "一年", + yy : "%d年" + } + }); +})); + + moment.lang('en'); + /************************************ Exposing Moment ************************************/ - function makeGlobal(deprecate) { - var warned = false, local_moment = moment; + function makeGlobal(shouldDeprecate) { /*global ender:false */ if (typeof ender !== 'undefined') { return; } - // here, `this` means `window` in the browser, or `global` on the server - // add `moment` as a global object via a string identifier, - // for Closure Compiler "advanced" mode - if (deprecate) { - global.moment = function () { - if (!warned && console && console.warn) { - warned = true; - console.warn( - "Accessing Moment through the global scope is " + - "deprecated, and will be removed in an upcoming " + - "release."); - } - return local_moment.apply(null, arguments); - }; - extend(global.moment, local_moment); + oldGlobalMoment = globalScope.moment; + if (shouldDeprecate) { + globalScope.moment = deprecate( + "Accessing Moment through the global scope is " + + "deprecated, and will be removed in an upcoming " + + "release.", + moment); } else { - global['moment'] = moment; + globalScope.moment = moment; } } // CommonJS module is defined if (hasModule) { module.exports = moment; - makeGlobal(true); } else if (typeof define === "function" && define.amd) { define("moment", function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal !== true) { - // If user provided noGlobal, he is aware of global - makeGlobal(module.config().noGlobal === undefined); + if (module.config && module.config() && module.config().noGlobal === true) { + // release the global variable + globalScope.moment = oldGlobalMoment; } return moment; }); + makeGlobal(true); } else { makeGlobal(); } diff --git a/server/sonar-web/src/main/js/widgets/bubble-chart.js b/server/sonar-web/src/main/js/widgets/bubble-chart.js index edafc4c6f66..de34819fdd5 100644 --- a/server/sonar-web/src/main/js/widgets/bubble-chart.js +++ b/server/sonar-web/src/main/js/widgets/bubble-chart.js @@ -128,23 +128,29 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; this.y = this.yLog() ? d3.scale.log() : d3.scale.linear(); this.size = d3.scale.linear(); - this.x - .domain(d3.extent(this.components(), function (d) { - return widget.getXMetric(d); - })) - .range([0, this.availableWidth]); - - this.y - .domain(d3.extent(this.components(), function (d) { - return widget.getYMetric(d); - })) - .range([this.availableHeight, 0]); - - this.size - .domain(d3.extent(this.components(), function (d) { - return widget.getSizeMetric(d); - })) - .range([5, 45]); + this.x.range([0, this.availableWidth]); + this.y.range([this.availableHeight, 0]); + this.size.range([5, 45]); + + if (this.components().length > 1) { + this.x.domain(d3.extent(this.components(), function (d) { + return widget.getXMetric(d); + })); + this.y.domain(d3.extent(this.components(), function (d) { + return widget.getYMetric(d); + })); + this.size.domain(d3.extent(this.components(), function (d) { + return widget.getSizeMetric(d); + })); + } else { + var singleComponent = this.components()[0], + xm = this.getXMetric(singleComponent), + ym = this.getYMetric(singleComponent), + sm = this.getSizeMetric(singleComponent); + this.x.domain([xm * 0.8, xm * 1.2]); + this.y.domain([ym * 0.8, ym * 1.2]); + this.size.domain([sm * 0.8, sm * 1.2]); + } // Create bubbles @@ -318,17 +324,25 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; // Update scales - this.x - .domain(d3.extent(this.components(), function (d) { - return widget.getXMetric(d); - })) - .range([0, this.availableWidth]); - - this.y - .domain(d3.extent(this.components(), function (d) { - return widget.getYMetric(d); - })) - .range([this.availableHeight, 0]); + this.x.range([0, this.availableWidth]); + this.y.range([this.availableHeight, 0]); + + if (this.components().length > 1) { + this.x.domain(d3.extent(this.components(), function (d) { + return widget.getXMetric(d); + })); + this.y.domain(d3.extent(this.components(), function (d) { + return widget.getYMetric(d); + })) + } else { + var singleComponent = this.components()[0], + xm = this.getXMetric(singleComponent), + ym = this.getYMetric(singleComponent), + sm = this.getSizeMetric(singleComponent); + this.x.domain([xm * 0.8, xm * 1.2]); + this.y.domain([ym * 0.8, ym * 1.2]); + this.size.domain([sm * 0.8, sm * 1.2]); + } if (this.x.domain()[0] === 0 && this.x.domain()[1] === 0) { diff --git a/server/sonar-web/src/main/js/widgets/stack-area.js b/server/sonar-web/src/main/js/widgets/stack-area.js index e5174e02462..5f625519bf1 100644 --- a/server/sonar-web/src/main/js/widgets/stack-area.js +++ b/server/sonar-web/src/main/js/widgets/stack-area.js @@ -152,7 +152,7 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; .attr('transform', trans(0, 54)); this.infoMetrics = []; - var prevX = 110; + var prevX = 120; this.metrics().forEach(function(d, i) { var infoMetric = widget.infoWrap.append('g'); @@ -175,7 +175,7 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; widget.infoMetrics.push(infoMetric); if (i % 3 === 2) { - prevX += (infoMetricText.node().getComputedTextLength() + 60); + prevX += (infoMetricText.node().getComputedTextLength() + 70); } }); @@ -232,7 +232,7 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; // Update info widget.infoDate - .text(d3.time.format('%b %d, %Y')(widget.data()[0][cl].x)); + .text(moment(widget.data()[0][cl].x).format('LL')); var snapshotValue = this.snapshots()[snapshotIndex].fy, totalValue = snapshotValue || (widget.stackDataTop[cl].y0 + widget.stackDataTop[cl].y); diff --git a/server/sonar-web/src/main/js/widgets/timeline.js b/server/sonar-web/src/main/js/widgets/timeline.js index 7825d853ca3..26faa583c46 100644 --- a/server/sonar-web/src/main/js/widgets/timeline.js +++ b/server/sonar-web/src/main/js/widgets/timeline.js @@ -240,7 +240,7 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; .attr('x2', sx); widget.infoDate - .text(d3.time.format('%b %d, %Y')(widget.data()[0][cl].x)); + .text(moment(widget.data()[0][cl].x).format('LL')); var metricsLines = widget.data().map(function(d, i) { return widget.metrics()[i] + ': ' + d[cl].yl; @@ -298,12 +298,12 @@ window.SonarWidgets = window.SonarWidgets == null ? {} : window.SonarWidgets; // Update metric lines var metricY = -1; this.infoMetrics.forEach(function(metric, i) { - var x = 110 + i * 170, + var x = 120 + i * 170, x2 = x + 170; if (x2 > widget.availableWidth) { metricY += 18; - x = 110; + x = 120; } metric diff --git a/server/sonar-web/src/main/less/api-documentation.less b/server/sonar-web/src/main/less/api-documentation.less index 98041a07289..d73a319aec8 100644 --- a/server/sonar-web/src/main/less/api-documentation.less +++ b/server/sonar-web/src/main/less/api-documentation.less @@ -1,6 +1,6 @@ -@import "variables"; -@import "mixins"; -@import "navigator/config"; +@import (reference) "variables"; +@import (reference) "mixins"; +@import (reference) "navigator/config"; @apiDocumentationSidebarWidth: 230px; diff --git a/server/sonar-web/src/main/less/coding-rules.less b/server/sonar-web/src/main/less/coding-rules.less index b58a9f94b7f..10551963557 100644 --- a/server/sonar-web/src/main/less/coding-rules.less +++ b/server/sonar-web/src/main/less/coding-rules.less @@ -1,6 +1,6 @@ -@import 'mixins'; -@import 'variables'; -@import 'navigator/config'; +@import (reference) 'mixins'; +@import (reference) 'variables'; +@import (reference) 'navigator/config'; @facetsHeight: 40px; @@ -379,10 +379,6 @@ textarea.coding-rules-markdown-description { display: none; } -.coding-rules-detail-not-ready { - color: @orange; -} - .coding-rules-list-tags { display: inline-block; vertical-align: top; diff --git a/server/sonar-web/src/main/less/component-viewer-source-colorizer.less b/server/sonar-web/src/main/less/component-viewer-source-colorizer.less index c3a56f66f40..f80ebe3fd83 100644 --- a/server/sonar-web/src/main/less/component-viewer-source-colorizer.less +++ b/server/sonar-web/src/main/less/component-viewer-source-colorizer.less @@ -1,4 +1,4 @@ -@import 'variables'; +@import (reference) 'variables'; .component-viewer-source { diff --git a/server/sonar-web/src/main/less/component-viewer.less b/server/sonar-web/src/main/less/component-viewer.less index 84ce9a28f63..b1268b39243 100644 --- a/server/sonar-web/src/main/less/component-viewer.less +++ b/server/sonar-web/src/main/less/component-viewer.less @@ -1,5 +1,6 @@ -@import "variables"; -@import "mixins"; +@import (reference) "variables"; +@import (reference) "mixins"; +@import (reference) "ui"; @headerHeight: 60px; @workspaceWidth: 250px; @@ -110,7 +111,6 @@ .component-viewer-source { position: relative; float: left; - border: 1px solid @barBorderColor; &.overflow { .box-sizing(border-box); @@ -119,6 +119,7 @@ .code { width: 100%; + border: 1px solid @barBorderColor; } .code th { @@ -354,6 +355,7 @@ position: relative; top: -2px; margin-left: 4px; + .link-no-underline; } .component-viewer-header-measures { @@ -375,6 +377,7 @@ border-left: 1px solid @barBackgroundColor; .box-sizing(border-box); background-color: lighten(@blue, 30%); + .link-no-underline; .transform-origin(0 0); .trans; @@ -399,6 +402,7 @@ display: block; padding: 11px 20px; white-space: nowrap; + .link-no-underline; a& { .trans(background); @@ -481,6 +485,7 @@ display: block; padding: 20px 10px; font-size: @iconFontSize; + .link-no-underline; } .component-viewer-header-more-actions { @@ -563,6 +568,7 @@ & > li > a.item { color: @baseFontColor; + .link-no-underline; .trans; &:hover { background-color: @barBackgroundColor; } @@ -587,6 +593,8 @@ .component-viewer-header-time-changes { padding: 10px; + + & > a { .link-no-underline; } } .component-viewer-header-sqale-rating { diff --git a/server/sonar-web/src/main/less/dashboard.less b/server/sonar-web/src/main/less/dashboard.less index aa181d7791e..65987d2f5ec 100644 --- a/server/sonar-web/src/main/less/dashboard.less +++ b/server/sonar-web/src/main/less/dashboard.less @@ -1,6 +1,6 @@ -@import "mixins"; -@import "variables"; -@import "ui"; +@import (reference) "mixins"; +@import (reference) "variables"; +@import (reference) "ui"; .dashboard-page { background-color: @barBackgroundColor; @@ -74,7 +74,7 @@ } #dashboard .widget-title a { - .highlighted-link; + .link-no-underline; } /*CONFIGURATION*/ @@ -390,17 +390,27 @@ // Widget Histogram .widget-barchar { + max-width: 180px; line-height: 1; td { vertical-align: middle !important; } - div.barchart div { height: 1em; } + div.barchart div { + min-width: 1px; + height: 1em; + } } .widget-measure-container .widget-barchar { margin-bottom: 10px; } +.widget-measure-container .widget-barchart-more { + margin-top: -5px; + margin-bottom: 10px; + padding-left: 5px; +} + // Description Widget .description-widget-project { diff --git a/server/sonar-web/src/main/less/icons.less b/server/sonar-web/src/main/less/icons.less index 271a0045538..271349d43a7 100644 --- a/server/sonar-web/src/main/less/icons.less +++ b/server/sonar-web/src/main/less/icons.less @@ -1,5 +1,6 @@ -@import "variables"; -@import "mixins"; +@import (reference) "variables"; +@import (reference) "mixins"; +@import (reference) "ui"; @font-face { font-family: 'sonar'; @@ -28,7 +29,7 @@ } a[class^="icon-"], a[class*=" icon-"] { - text-decoration: none !important; + .link-no-underline; } @@ -275,6 +276,11 @@ a[class^="icon-"], a[class*=" icon-"] { &.icon-checkbox-single:after { content: "\e60e"; } } +// Used for align elements +.icon-checkbox-invisible { + visibility: hidden; +} + /* * Common @@ -509,11 +515,6 @@ a[class^="icon-"], a[class*=" icon-"] { content: "\f016"; font-size: @iconFontSize; } -.icon-cross-project:before { - content: "\f0ec"; - color: @purple; - font-size: @iconFontSize; -} /* diff --git a/server/sonar-web/src/main/less/layout.less b/server/sonar-web/src/main/less/layout.less index 8f4c36156a8..75579aa28af 100644 --- a/server/sonar-web/src/main/less/layout.less +++ b/server/sonar-web/src/main/less/layout.less @@ -1,6 +1,6 @@ -@import "mixins"; -@import "variables"; -@import "ui"; +@import (reference) "mixins"; +@import (reference) "variables"; +@import (reference) "ui"; @media print { /* ------------------- PRINT ------------------- */ @@ -70,8 +70,8 @@ body, a { } #hd a { - .base-link; - color: #e5e5e5; + color: #fff; + .link-no-underline; &.selected { color: @blue; } } @@ -145,11 +145,7 @@ body, a { } #bc li a { - text-decoration: none; -} - -#bc li a:hover, #bc li a:focus { - text-decoration: underline; + .link-no-underline; } #crumbs-ops { @@ -163,11 +159,7 @@ body, a { } #crumbs-ops li a { - text-decoration: none; -} - -#crumbs-ops li a:hover, #crumbs-ops li a:focus { - text-decoration: underline; + .link-no-underline; } #hd .dropdown-menu a { @@ -224,7 +216,7 @@ ul.sidebar li { } ul.sidebar li > a { - .highlighted-link; + .link-no-underline; } ul.sidebar li.active { diff --git a/server/sonar-web/src/main/less/mixins.less b/server/sonar-web/src/main/less/mixins.less index ef2dc10a7b5..66349f4b655 100644 --- a/server/sonar-web/src/main/less/mixins.less +++ b/server/sonar-web/src/main/less/mixins.less @@ -1,4 +1,4 @@ -@import "variables"; +@import (reference) "variables"; .clearfix() { &:before, &:after { display: table; content: ""; line-height: 0; } diff --git a/server/sonar-web/src/main/less/navigator.less b/server/sonar-web/src/main/less/navigator.less index ab9401b0478..ddb1478deaa 100644 --- a/server/sonar-web/src/main/less/navigator.less +++ b/server/sonar-web/src/main/less/navigator.less @@ -1,5 +1,5 @@ -@import "mixins"; -@import "variables"; +@import (reference) "mixins"; +@import (reference) "variables"; @import "navigator/config"; @import "navigator/base"; diff --git a/server/sonar-web/src/main/less/navigator/filters.less b/server/sonar-web/src/main/less/navigator/filters.less index e049c8ad87e..f56739867ef 100644 --- a/server/sonar-web/src/main/less/navigator/filters.less +++ b/server/sonar-web/src/main/less/navigator/filters.less @@ -209,10 +209,11 @@ label { display: block; padding: 5px @navigatorFilterPadding; - transition: background 0.3s ease; cursor: pointer; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; + .trans(background); &:hover, &.current { @@ -228,14 +229,14 @@ vertical-align: text-bottom; } - & > span { - display: inline-block; - vertical-align: top; - width: 86%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } +// & > span { +// display: inline-block; +// vertical-align: top; +// width: 86%; +// white-space: nowrap; +// overflow: hidden; +// text-overflow: ellipsis; +// } &.special { font-style: italic; diff --git a/server/sonar-web/src/main/less/quality-gates.less b/server/sonar-web/src/main/less/quality-gates.less index fb56fbc7be7..070216d1b21 100644 --- a/server/sonar-web/src/main/less/quality-gates.less +++ b/server/sonar-web/src/main/less/quality-gates.less @@ -1,6 +1,6 @@ -@import "variables"; -@import "mixins"; -@import "navigator/config"; +@import (reference) "variables"; +@import (reference) "mixins"; +@import (reference) "navigator/config"; @qualityGateSidebarWidth: 230px; @@ -125,4 +125,4 @@ border-left: none !important; border-right: none !important; } -}
\ No newline at end of file +} diff --git a/server/sonar-web/src/main/less/select2-sonar.less b/server/sonar-web/src/main/less/select2-sonar.less index fed92898f3d..d6b89c29cc1 100644 --- a/server/sonar-web/src/main/less/select2-sonar.less +++ b/server/sonar-web/src/main/less/select2-sonar.less @@ -1,5 +1,5 @@ -@import "mixins"; -@import "variables"; +@import (reference) "mixins"; +@import (reference) "variables"; @imagesPath: '../images/select2.png'; @imagesPath2x: '../images/select2x2.png'; diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less index 8a607cfa136..db1329484a0 100644 --- a/server/sonar-web/src/main/less/style.less +++ b/server/sonar-web/src/main/less/style.less @@ -1,6 +1,6 @@ -@import "mixins"; -@import "variables"; -@import "ui"; +@import (reference) "mixins"; +@import (reference) "variables"; +@import (reference) "ui"; /* * SonarQube, open source software quality management tool. @@ -40,6 +40,7 @@ border-top: 1px solid #fff; font-size: 85%; padding: 10px 0; + line-height: 1.4; } .ie-warn { @@ -53,10 +54,6 @@ text-decoration: underline; } -#ftlinks a { - color: #444; -} - /* GENERAL */ @@ -64,6 +61,11 @@ a { cursor: pointer; } +a.icon-favorite, +a.icon-not-favorite { + .link-no-underline; +} + .loading { background: url("../images/loading.gif") no-repeat 4px 2px; color: #444; @@ -386,6 +388,7 @@ table.data > thead > tr > th { vertical-align: top; font-size: 93%; padding: 4px 7px 4px 3px; + line-height: 1.4; text-transform: uppercase; & > .small { @@ -471,7 +474,6 @@ table.sortable .sortcol { padding-left: 15px; background-repeat: no-repeat; background-position: right center; - text-decoration: underline; } table.sortable .sortasc { @@ -1792,10 +1794,6 @@ ul.bullet li { padding: 0; } -.tabs li a { - outline: none; -} - .tabs2 li a, .tabs li a { float: left; color: #777; @@ -1803,6 +1801,7 @@ ul.bullet li { height: 17px; margin: 0 1px 0 0; padding: 1px 5px; + .link-no-underline; } .tabs2 li a.selected, .tabs li a.selected, .tabs .ui-tabs-active a { @@ -1864,6 +1863,14 @@ ul.bullet li { color: #fff; margin: 0; padding: 0 3px; + + a > & { + margin-bottom: -1px; + border-bottom: 1px solid @orange; + .trans; + + &:hover { opacity: 0.8; } + } } .alert_ERROR { @@ -1871,6 +1878,14 @@ ul.bullet li { color: #fff; margin: 0; padding: 0 3px; + + a > & { + margin-bottom: -1px; + border-bottom: 1px solid @red; + .trans; + + &:hover { opacity: 0.8; } + } } #comparison span.best { @@ -2076,11 +2091,7 @@ table.matrix tbody td.title { } a.nolink { - text-decoration: none; -} - -a.nolink:hover { - text-decoration: underline; + .link-no-underline; } h1 strong, .dashbox .title, .gwt-SourcePanel .sources .msg li strong { @@ -2222,11 +2233,6 @@ table.nowrap td.small, td.nowrap.small, th.nowrap.small { background: #FFFBCC; } -.action { - text-decoration: underline; - cursor: pointer; -} - /* Used on links which are located inside a dense text place or in tables */ /* in order to rapidly identify them */ .link-action { @@ -2850,6 +2856,7 @@ div.rule-title { line-height: @iconSmallFontSize; color: #fff; opacity: 0.5; + .link-no-underline; &:hover { opacity: 1; } } diff --git a/server/sonar-web/src/main/less/ui.less b/server/sonar-web/src/main/less/ui.less index 571f245e061..374bb140db2 100644 --- a/server/sonar-web/src/main/less/ui.less +++ b/server/sonar-web/src/main/less/ui.less @@ -1,5 +1,5 @@ -@import "variables"; -@import "mixins"; +@import (reference) "variables"; +@import (reference) "mixins"; @@ -107,12 +107,12 @@ select::-moz-focus-inner, input::-moz-focus-inner, button::-moz-focus-inner { .link; } -.highlighted-link { +.highlighted-link() { color: @darkBlue; .link; } -.underlined-link { +.underlined-link() { .highlighted-link; border-bottom: 1px solid @lightBlue; @@ -139,14 +139,19 @@ select::-moz-focus-inner, input::-moz-focus-inner, button::-moz-focus-inner { } } -.active-link { +.link-no-underline() { border-bottom: none; - font-weight: 500; } + a { cursor: pointer; - .highlighted-link; + .underlined-link; + + &.active-link { + border-bottom: none; + font-weight: 500; + } } @@ -238,6 +243,8 @@ input[type=button] { } } +.button { line-height: 22px; } + .button-red { &:hover, &:focus { border-color: #900; @@ -267,7 +274,8 @@ input[type=button] { font-size: 0; white-space: nowrap; - & > button { + & > button, + & > .button { position: relative; z-index: 2; display: inline-block; @@ -283,11 +291,16 @@ input[type=button] { } } - & > button + button { + & > .button { line-height: 16px; } + + & > button + button, + & > button + .button, + & > .button + button, + & > .button + .button { margin-left: -1px; } - & > a { + & > a:not(.button) { vertical-align: middle; margin: 0 8px; font-size: @smallFontSize; @@ -325,25 +338,38 @@ input[type=button] { color: #fff; font-weight: 300; text-align: center; + + a > & { + margin-bottom: -1px; + border-bottom: 1px solid; + .trans; + + &:hover { opacity: 0.8; } + } } .rating-A { background-color: #00AA00; + a & { border-bottom-color: #00AA00; } } .rating-B { background-color: #80CC00; + a & { border-bottom-color: #80CC00; } } .rating-C { background-color: #FFEE00; color: @baseFontColor; + a & { border-bottom-color: #FFEE00; } } .rating-D { background-color: #F77700; + a & { border-bottom-color: #F77700; } } .rating-E { background-color: #EE0000; + a & { border-bottom-color: #EE0000; } } diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb index a675fd5f3a9..7690cea09be 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb @@ -109,6 +109,8 @@ class IssuesController < ApplicationController def copy_form require_parameters :id @filter = find_filter(params[:id].to_i) + @filter.setUser(nil) + @filter.setShared(false) render :partial => 'issues/filter_copy_form' end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/measures_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/measures_controller.rb index 2ec278531e9..7c955be664b 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/measures_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/measures_controller.rb @@ -165,6 +165,8 @@ class MeasuresController < ApplicationController def copy_form require_parameters :id @filter = find_filter(params[:id]) + @filter.shared = false + @filter.user_id = nil render :partial => 'measures/copy_form' end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb index 0afc33579e6..150b37a3e7e 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb @@ -408,7 +408,11 @@ class ProjectController < ApplicationController name = event.name resource_id = event.resource_id events = find_events(event) - Event.delete(events.map { |e| e.id }) + Event.transaction do + events.map { |e| e.id }.each_slice(999) do |safe_for_oracle_ids| + Event.delete(safe_for_oracle_ids) + end + end flash[:notice] = message('project_history.event_deleted', :params => name) redirect_to :action => 'history', :id => resource_id diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/application_helper.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/application_helper.rb index 9a58a14981d..5ea88ea1f96 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/application_helper.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/application_helper.rb @@ -129,7 +129,7 @@ module ApplicationHelper def boolean_icon(boolean_value, options={}) if boolean_value - image_tag('tick.png', options) + "<i class='icon-check'></i>" elsif options[:display_false] image_tag('cross.png', options) else @@ -947,7 +947,7 @@ module ApplicationHelper total = pagination.total.to_i page_index = pagination.pageIndex() ? pagination.pageIndex().to_i : 1 pages = pagination.pages().to_i - results_html = options[:url_results] ? message('x_results', :params => "<a class='underlined-link' href='#{options[:url_results]}'>#{total}</a>") : message('x_results', :params => [total]) + results_html = options[:url_results] ? message('x_results', :params => "<a href='#{options[:url_results]}'>#{total}</a>") : message('x_results', :params => [total]) html = '<tfoot' html += " id='#{options[:id]}'" if options[:id] diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb index ca4472c524b..b316df15a2e 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb @@ -28,7 +28,7 @@ module MeasuresHelper if column.period html += "<br><span class='note'>#{Api::Utils.period_abbreviation(column.period)}</small>" end - if filter.sort_key==column.key + if column.sort? && filter.sort_key==column.key html << (filter.sort_asc? ? ' <i class="icon-sort-asc"></i>' : ' <i class="icon-sort-desc"></i>') end "<th class='#{column.align} #{column.title_css}'>#{html}</th>" @@ -50,9 +50,9 @@ module MeasuresHelper end elsif column.key=='name' - "#{qualifier_icon(row.snapshot)} #{link_to(h(row.snapshot.resource.name(true)), {:controller => 'dashboard', :id => row.snapshot.resource_id}, {:title => h(row.snapshot.resource.key), :class => 'underlined-link'})}" + "#{qualifier_icon(row.snapshot)} #{link_to(h(row.snapshot.resource.name(true)), {:controller => 'dashboard', :id => row.snapshot.resource_id}, {:title => h(row.snapshot.resource.key)})}" elsif column.key=='short_name' - "#{qualifier_icon(row.snapshot)} #{link_to(h(row.snapshot.resource.name(false)), {:controller => 'dashboard', :id => row.snapshot.resource_id}, {:title => h(row.snapshot.resource.key), :class => 'highlighted-link'})}" + "#{qualifier_icon(row.snapshot)} #{link_to(h(row.snapshot.resource.name(false)), {:controller => 'dashboard', :id => row.snapshot.resource_id}, {:title => h(row.snapshot.resource.key)})}" elsif column.key=='date' human_short_date(row.snapshot.created_at) elsif column.key=='project_creation_date' diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/models/measure_filter.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/models/measure_filter.rb index a0a1a25fc0b..0ec73aabe27 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/models/measure_filter.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/models/measure_filter.rb @@ -158,8 +158,11 @@ class MeasureFilter < ActiveRecord::Base @criteria = self.data.split(CRITERIA_SEPARATOR).inject(HashWithIndifferentAccess.new) do |h, s| k, v=s.split('=') if k && v - # Empty values are removed - v=v.split(CRITERIA_KEY_VALUE_SEPARATOR).select{|v| !v.empty?} if v.include?(CRITERIA_KEY_VALUE_SEPARATOR) + # nameSearch can contains comma, in this case we should not split the value + if k != 'nameSearch' + # Empty values are removed + v=v.split(CRITERIA_KEY_VALUE_SEPARATOR).select{|v| !v.empty?} if v.include?(CRITERIA_KEY_VALUE_SEPARATOR) + end h[k]=v end h diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/admin_dashboards/_list.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/admin_dashboards/_list.html.erb index 5490789acc7..4f474906a13 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/admin_dashboards/_list.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/admin_dashboards/_list.html.erb @@ -24,12 +24,12 @@ </td> <td class="order"> <% if index > 0 %> - <%= link_to image_tag('blue-up.png'), {:action => :up, :id => active.id}, :method => :post, :id => "up-#{u active.name}" %> + <%= link_to '', {:action => :up, :id => active.id}, :method => :post, :id => "up-#{u active.name}", :class => 'icon-move-up' %> <% else %> <%= image_tag('transparent_16.gif') %> <% end %> <% if index < active_dashboards.size-1 %> - <%= link_to image_tag('blue-down.png'), {:action => :down, :id => active.id}, :method => :post, :id => "down-#{u active.name}" %> + <%= link_to '', {:action => :down, :id => active.id}, :method => :post, :id => "down-#{u active.name}", :class => 'icon-move-down' %> <% else %> <%= image_tag('transparent_16.gif') %> <% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb index 9a3b6e3d548..71a1b4463a1 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb @@ -196,7 +196,7 @@ %> <th style="text-align: center; vertical-align: top; line-height: 1.5;"> <span class="no-transform"> - <a class="underlined-link" href="<%= ApplicationController.root_context -%>/dashboard/index/<%= s.resource.key -%>"><%= h s.resource.name(true) -%></a> + <a href="<%= ApplicationController.root_context -%>/dashboard/index/<%= s.resource.key -%>"><%= h s.resource.name(true) -%></a> <br/> <span class="note"><b><%= event ? event.name : message('comparison.version.latest') -%></b></span> <br/> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/_list_table_header.rhtml b/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/_list_table_header.rhtml index 17838c3e097..5d74204a211 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/_list_table_header.rhtml +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/_list_table_header.rhtml @@ -16,7 +16,7 @@ <th><% if logged_in? %><%= link_to_favourite(@snapshot.project) -%><% end %></th> <th class="left text <%= 'sortfirstasc' if @components_configuration.sorted_by_project_name? -%>"> <%= qualifier_icon(@snapshot) -%> - <a class="underlined-link no-transform" x="<%= u(@snapshot.project.name) -%>" href="<%= ApplicationController.root_context + "/dashboard/index/#{@snapshot.project.id}" -%>"><%= h @snapshot.project.name -%></a> + <a class="no-transform" x="<%= u(@snapshot.project.name) -%>" href="<%= ApplicationController.root_context + "/dashboard/index/#{@snapshot.project.id}" -%>"><%= h @snapshot.project.name -%></a> </th> <% @columns.each do |column| %> <%= get_header_content(column, @snapshot) -%> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/index.html.erb index f85b44f48f2..f24a454aa46 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/components/index.html.erb @@ -53,7 +53,7 @@ <td class="left" x="<%= u(snapshot.project.name) -%>"> <%= qualifier_icon(snapshot) %> <% if snapshot.display_dashboard? %> - <%= link_to_resource(project, h(snapshot.project.name), :class => 'underlined-link') %> + <%= link_to_resource(project, h(snapshot.project.name)) %> <% else %> <%= h snapshot.project.name %> <% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/_header.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/_header.html.erb index 1dab108bcc4..900075c19f0 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/_header.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboard/_header.html.erb @@ -2,16 +2,16 @@ <div class="line-block"> <% if logged_in? %> - <ul class="operations noprint"> + <div class="operations noprint button-group"> <% if back %> - <li><%= link_to message('dashboard.back_to_dashboard'), dashboard_action(:index) -%></li> + <%= link_to message('dashboard.back_to_dashboard'), dashboard_action(:index), :class => 'button' -%> <% else %> <% if @dashboard.editable_by?(current_user) %> - <li><%= link_to message('dashboard.configure_widgets'), dashboard_action(:configure) -%></li> + <%= link_to message('dashboard.configure_widgets'), dashboard_action(:configure), :class => 'button' -%> <% end %> <% end %> - <li class="last"><%= link_to message('dashboard.manage_dashboards'), {:controller => :dashboards, :action => :index, :resource => (@resource.id if @resource) } -%></li> - </ul> + <%= link_to message('dashboard.manage_dashboards'), {:controller => :dashboards, :action => :index, :resource => (@resource.id if @resource) }, :class => 'button' -%> + </div> <% end %> <% if @snapshot %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_available_dashboards.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_available_dashboards.html.erb index 073f91dc35a..5737650fb80 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_available_dashboards.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_available_dashboards.html.erb @@ -19,7 +19,7 @@ <tr id="dashboard-<%= dashboard.id -%>" class="<%= cycle('even', 'odd', :name => 'shared') -%>"> <td> <%= link_to h(dashboard.name(true)), {:controller => :dashboard, :action => :index, :did => dashboard.id, :id => (resource_id unless dashboard.global?)}, - :id => "view-#{u dashboard.name}", :class => 'underlined-link' %> + :id => "view-#{u dashboard.name}" %> <div class="description"><%= h dashboard.description -%></div> </td> <td class="shared"> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb index 6a36cbe0706..68240dea729 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb @@ -19,7 +19,7 @@ <tr id="dashboard-<%= dashboard.id -%>" class="<%= cycle('even', 'odd', :name => 'dashboard') -%>"> <td> <%= link_to h(dashboard.name(true)), {:controller => :dashboard, :action => :index, :did => dashboard.id, :id => (resource_id unless dashboard.global?)}, - :id => "view-#{u dashboard.name}", :class => 'underlined-link' %> + :id => "view-#{u dashboard.name}" %> <div class="description"><%= h dashboard.description -%></div> </td> <td class="shared"> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/_severity.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/_severity.html.erb index cb890b1cf7a..8625066e0c4 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/_severity.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/_severity.html.erb @@ -2,7 +2,7 @@ <tr class="<%= css -%> <%= 'selected' if selected -%>"> <td><i class="icon-severity-<%= severity.downcase -%>"></i></td> <td> - <%= link_to message("severity.#{severity}"), {:controller => :drilldown, :action => :issues, :id => @resource.id, :severity => (selected ? nil : severity), :period => @period}, :class => 'underlined-link' %> + <%= link_to message("severity.#{severity}"), {:controller => :drilldown, :action => :issues, :id => @resource.id, :severity => (selected ? nil : severity), :period => @period} %> </td> <td style="padding-left: 10px;" align="right" nowrap> <%= @period ? format_variation(measure, :index => @period, :style => 'light') : format_measure(measure) -%> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb index 8aef0011a5e..7c588467e66 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb @@ -100,8 +100,7 @@ {:controller => :drilldown, :action => :issues, :id => @resource.id, :rule => (selected ? nil : rule.key), :rule_sev => (selected ? nil : rule_measure.severity), :sid => nil, :severity => @severity, :period => @period, :rids => (selected ? nil : @selected_rids)}, - :title => "#{rule.plugin_name}: #{rule.plugin_rule_key}", - :class => 'underlined-link' + :title => "#{rule.plugin_name}: #{rule.plugin_rule_key}" ) -%> </td> <td class="right" nowrap="nowrap"> @@ -153,7 +152,7 @@ <a href="<%= url_for :controller => 'dashboard', :action => 'index', :id => resource.id, :period => @period, :metric => (@metric && @metric.key), :rule => @rule ? @rule.id : @severity -%>" onclick="window.open(this.href,'resource-<%= resource.key.parameterize -%>','scrollbars=1,resizable=1');return false;" - id="popup-<%= resource.key.parameterize -%>" + id="popup-<%= resource.key.parameterize -%>" class="nolink" target="_blank"><i class="icon-detach" title="<%= message('new_window') -%>"></i></a> <% else %> <%= link_to(image_tag('zoom.png'), {:id => resource.id}, {:class => 'nolink'}) %> @@ -161,9 +160,9 @@ <%= qualifier_icon(resource) -%> <% if resource.source_code? %> <a href="#" alt="<%= h resource.name(true) -%>" title="<%= h resource.name(true) -%>" data-key="<%= resource.key -%>" - class="js-drilldown-link underlined-link"><%= h resource.name(false) %></a> + class="js-drilldown-link"><%= h resource.name(false) %></a> <% else %> - <%= link_to(h(resource.name), {:only_path => true, :overwrite_params => {:rids => (selected ? rids-[resource.id] : rids+[resource.id])}}, :class => 'underlined-link') -%> + <%= link_to(h(resource.name), {:only_path => true, :overwrite_params => {:rids => (selected ? rids-[resource.id] : rids+[resource.id])}}) -%> <% end %> </td> <td class="right" nowrap> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb index 0bea2a00709..453e9e8ba65 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/drilldown/measures.html.erb @@ -77,7 +77,7 @@ <% if resource.source_code? %> <a href="<%= url_for :controller => 'dashboard', :action => 'index', :id => resource.id, :period => @period, :metric => (@metric && @metric.key), :rule => @rule ? @rule.id : @severity -%>" onclick="window.open(this.href,'resource-<%= resource.key.parameterize -%>','scrollbars=1,resizable=1');return false;" - id="popup-<%= resource.key.parameterize -%>" + id="popup-<%= resource.key.parameterize -%>" class="nolink" target="_blank"><i class="icon-detach" title="<%= message('new_window') -%>"></i></a> <% else %> <%= link_to(image_tag('zoom.png'), {:id => resource.id, :metric => @metric.id}, {:class => 'nolink'}) -%> @@ -85,9 +85,9 @@ <%= qualifier_icon(resource) -%> <% if resource.source_code? %> <a href="#" title="<%= h resource.name(true) -%>" data-key="<%= resource.key -%>" - class="js-drilldown-link underlined-link"><%= h resource.name(false) %></a> + class="js-drilldown-link"><%= h resource.name(false) %></a> <% else %> - <%= link_to(h(resource.name), params.merge({:only_path => true, :rids => (selected ? rids-[resource.id] : rids+[resource.id])}), :class => 'underlined-link') -%> + <%= link_to(h(resource.name), params.merge({:only_path => true, :rids => (selected ? rids-[resource.id] : rids+[resource.id])})) -%> <% end %> </td> <td class="right"> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/groups/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/groups/index.html.erb index 671e29f1ab2..3d75fcbfdba 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/groups/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/groups/index.html.erb @@ -22,10 +22,10 @@ <table class="data width100 sortable" id="groups"> <thead> <tr> - <th class="left sortfirstasc">Name</th> - <th class="left">Description</th> - <th class="left">Members</th> - <th class="operations nosort" nowrap>Operations</th> + <th class="left sortfirstasc"><a>Name</a></th> + <th class="left"><a>Description</a></th> + <th class="left"><a>Members</a></th> + <th class="operations nosort" nowrap><a>Operations</a></th> </tr> </thead> <tbody> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/_filter_shared_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/_filter_shared_form.html.erb index d90b9660b73..24c3891792a 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/_filter_shared_form.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/_filter_shared_form.html.erb @@ -22,7 +22,7 @@ <% end %> <% if Internal.issues.canUserShareIssueFilter() %> <div class="modal-field"> - <% if !@filter || @filter.user == current_user.login %> + <% if !@filter || @filter.user.nil? || @filter.user == current_user.login %> <label for="shared"><%= message('issue_filter.form.share') -%></label> <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if (@filter && @filter.shared) -%>/> <% else %> @@ -30,4 +30,4 @@ <% end %> </div> <% end %> -</div>
\ No newline at end of file +</div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb index c76a3a81826..fac32ece75d 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb @@ -27,6 +27,7 @@ _.extend(window.SS, { currentUser: '<%= current_user.login if current_user -%>', + currentUserName: '<%= current_user.name if current_user -%>', severities: <%= RulesConfigurationController::RULE_PRIORITIES.to_json.html_safe -%>, statuses: <%= @options_for_statuses.to_json.html_safe -%>, resolutions: <%= @options_for_resolutions.to_json.html_safe -%>, diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_footer.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_footer.html.erb index 1512e0ea45a..e4985adb843 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_footer.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_footer.html.erb @@ -2,7 +2,7 @@ <div style="position:fixed;z-index:99999;top:0;bottom:0;left:0;right:0;background:#fff;"> <div style="margin-top:150px;text-align:center;line-height:1.4;color:#333;"> The web interface cannot be displayed because your browser is not supported.<br> - Please switch to a <a class="underlined-link" target="_blank" + Please switch to a <a target="_blank" href="http://docs.codehaus.org/x/zYHEBg">supported version or another supported browser</a>. </div> </div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb index 25be7638c90..193a0a0096a 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb @@ -22,6 +22,9 @@ <link href="<%= ApplicationController.root_context -%>/css/sonar.css" rel="stylesheet" media="all"> <%= yield :style -%> + <script> + var pageLang = '<%= I18n.locale.to_s.gsub(/-/, '_') -%>'; + </script> <script src="<%= ApplicationController.root_context -%>/js/sonar.js"></script> <script> var baseUrl = '<%= ApplicationController.root_context -%>'; diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_display_list.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_display_list.html.erb index 8a63f3807dd..3dae12f9a0e 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_display_list.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/_display_list.html.erb @@ -222,11 +222,11 @@ </tbody> <% if widget_id %> <%= table_pagination(filter.pagination, :colspan => colspan, :id => "measure_filter_foot#{widget_id}", :include_loading_icon => true) { |label, page_id| - link_to_function label, "refreshList#{widget_id}('#{filter.criteria[:sort]}', #{filter.criteria[:asc]}, #{page_id})", :class => 'underlined-link' + link_to_function label, "refreshList#{widget_id}('#{filter.criteria[:sort]}', #{filter.criteria[:asc]}, #{page_id})" } -%> <% else %> <%= table_pagination(filter.pagination, :colspan => colspan, :id => "measure_filter_foot#{widget_id}", :include_loading_icon => true) { |label, page_id| - link_to(label, filter.criteria.merge({:page => page_id}), :class => 'underlined-link') + link_to(label, filter.criteria.merge({:page => page_id})) } -%> <% end %> </table> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb index 13fbbdfdbf1..e2b250d32e8 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/measures/search.html.erb @@ -127,6 +127,8 @@ <% end %> <% end %> - { key: 'nameSearch', value: '<%= h @filter.criteria['nameSearch'] -%>' } + <% puts "### " + @filter.criteria.inspect %> + + { key: 'nameSearch', value: '<%= @filter.criteria['nameSearch'] -%>' } ]; </script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/metrics/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/metrics/index.html.erb index 05f02a80d92..d308a1f9161 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/metrics/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/metrics/index.html.erb @@ -19,12 +19,12 @@ <table class="sortable data width100" id="metrics"> <thead> <tr> - <th class="left">Key</th> - <th class="left sortfirstasc">Name</th> - <th class="left">Description</th> - <th class="left">Domain</th> - <th class="left">Type</th> - <th class="left nosort">Operations</th> + <th class="left"><a>Key</a></th> + <th class="left sortfirstasc"><a>Name</a></th> + <th class="left"><a>Description</a></th> + <th class="left"><a>Domain</a></th> + <th class="left"><a>Type</a></th> + <th class="left nosort"><a>Operations</a></th> </tr> </thead> <tbody> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb index 1a4f834e45b..fa44944563a 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb @@ -8,15 +8,11 @@ <%= render :partial => 'roles/tabs', :locals => {:selected_tab=>'Permission templates'} %> <br/> <div class="line-block marginbottom10"> - <ul class="operations"> - <li> - <%= link_to message('permission_template.set_default_templates'), {:action => :default_templates_form, :qualifiers => @root_qualifiers}, - :id => 'configure-defaults-permission-template', :class => 'open-modal link-action' %> - </li> - <li class="last"> - <%= link_to message('create'), {:action => :create_form}, :id => 'create-link-permission-template', :class => 'open-modal link-action' %> - </li> - </ul> + <div class="button-group operations"> + <%= link_to message('permission_template.set_default_templates'), {:action => :default_templates_form, :qualifiers => @root_qualifiers}, + :id => 'configure-defaults-permission-template', :class => 'open-modal link-action button' %> + <%= link_to message('create'), {:action => :create_form}, :id => 'create-link-permission-template', :class => 'open-modal link-action button' %> + </div> </div> <table class="data width100" id="permission-templates"> <thead> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb index e8a77e22cd9..536e6eb445e 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb @@ -1,17 +1,11 @@ <div class="page"> <div class="line-block marginbottom10"> - <ul style="float: right" class="operations"> - <li> - <i class="icon-compare"></i> - <a href="profiles/compare" id="compare-link"><%= message('quality_profiles.compare_profiles') -%></a> - </li> + <div style="float: right" class="operations button-group"> + <a href="profiles/compare" id="compare-link" class="button"><i class="icon-compare"></i> <%= message('quality_profiles.compare_profiles') -%></a> <% if profiles_administrator? %> - <li class="last"> - <i class="icon-restore"></i> - <a href="profiles/restore_form" class="open-modal" id="restore-link"><%= message('quality_profiles.restore_profile') -%></a> - </li> + <a href="profiles/restore_form" class="open-modal button" id="restore-link"><i class="icon-restore"></i> <%= message('quality_profiles.restore_profile') -%></a> <% end %> - </ul> + </div> </div> <% diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb index 141d250d491..15719704de4 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb @@ -71,7 +71,7 @@ <%= paginate_java(paging, :colspan => 3, :id => "issue-filter-foot-#{widget_id}", :include_loading_icon => true, :url_results => url_for_issues(search_options.except('pageSize', 'pageIndex', 'table_limit', 'widget_id')) ) { |label, page_id| <<EOF -<a class="underlined-link" href="#" onclick="$j.ajax({ url:'#{url_for(link_params.merge({:pageIndex => page_id}))}', type:'post', success:function(response){$j('#issues-widget-#{widget_id}').html(response);}}); return false;">#{label}</a> +<a href="#" onclick="$j.ajax({ url:'#{url_for(link_params.merge({:pageIndex => page_id}))}', type:'post', success:function(response){$j('#issues-widget-#{widget_id}').html(response);}}); return false;">#{label}</a> EOF } -%> </table> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project_roles/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project_roles/index.html.erb index 93a78829954..5aee7f30416 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project_roles/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project_roles/index.html.erb @@ -3,13 +3,11 @@ <% end %> <div> - <ul class="operations"> - <li class="last"> - <%= link_to message('projects_role.apply_template'), {:controller => :roles, :action => :apply_template_form, :components => [@project.key], :names => @project.name, - :results_count => 1}, - :id => "apply-template-#{u @project.kee}", :class => 'open-modal link-action' %> - </li> - </ul> + <div class="operations button-group"> + <%= link_to message('projects_role.apply_template'), {:controller => :roles, :action => :apply_template_form, :components => [@project.key], :names => @project.name, + :results_count => 1}, + :id => "apply-template-#{u @project.kee}", :class => 'open-modal link-action button' %> + </div> <h1 class="admin-page-title"><%= message('project_roles.page') -%></h1> <p class="admin-page-description"><%= message('project_roles.page.description2') -%></p> </div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb index 200b74e46e4..64e2f4483fd 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb @@ -36,14 +36,12 @@ </div> <div id="project-roles-operations" style="float: right;"> - <ul class="operations"> - <li class="last"> - <%= link_to message('projects_role.bulk_change'), {:action => :apply_template_form, :names => @components_names, - :keys => @components_keys, :qualifiers => @components_qualifiers, - :results_count => @query_result.paging.total}, - :id => 'apply-template-modal', :class => 'open-modal link-action' %> - </li> - </ul> + <div class="operations button-group"> + <%= link_to message('projects_role.bulk_change'), {:action => :apply_template_form, :names => @components_names, + :keys => @components_keys, :qualifiers => @components_qualifiers, + :results_count => @query_result.paging.total}, + :id => 'apply-template-modal', :class => 'open-modal link-action button' %> + </div> </div> <table class="data width100" id="projects"> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb index 9399514f7bc..9b7c63199ce 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/updatecenter/system_updates.html.erb @@ -69,34 +69,25 @@ Follow those steps to upgrade SonarQube from version <%= sonar_version -%> to version <%= release.getVersion() -%> : <ol class="bulletpoints"> + <li>Stop SonarQube</li> + <li><%= link_to 'Download', release.getDownloadUrl(), :class => 'external' -%> and install + SonarQube <%= release.getVersion() -%> after having carefully read the <a href="http://docs.codehaus.org/display/SONAR/Upgrading" class="external">upgrade guide</a>. + </li> <% update.getIncompatiblePlugins().each do |incompatible_plugin| %> - <li> - <form method="post" - action="<%= ApplicationController.root_context -%>/updatecenter/uninstall?key=<%= incompatible_plugin.getKey() -%>&from=system_updates" - style="display: inline-block"> - <%= image_tag 'warning.png' -%> - <input type="submit" value="Uninstall" class="red-button" onClick="return submitForm(this);"/> - the plugin <%= incompatible_plugin.getName() -%> which is not compatible with - SonarQube <%= release.getVersion() -%>. - </form> - </li> + <li> + Uninstall the plugin <%= incompatible_plugin.getName() -%> which is not compatible with + SonarQube <%= release.getVersion() -%>. + </form> + </li> <% end %> <% update.getPluginsToUpgrade().each do |plugin_to_upgrade| %> - <li> - <form method="post" id="upgrade-form-<%= plugin_to_upgrade.getArtifact().getKey() -%>" - action="<%= ApplicationController.root_context -%>/updatecenter/install?key=<%= plugin_to_upgrade.getArtifact().getKey() -%>&version=<%= plugin_to_upgrade.getVersion() -%>&from=system_updates" - style="display: inline-block"> - <input type="submit" id="upgrade-submit-<%= plugin_to_upgrade.getArtifact().getKey() -%>" value="Upgrade" onClick="return submitForm(this);"/> - the plugin <%= plugin_to_upgrade.getArtifact().getName() -%> to - version <%= plugin_to_upgrade.getVersion() -%> - </form> - </li> - <% end %> - <li>Stop Sonar</li> - <li><%= link_to 'Download', release.getDownloadUrl(), :class => 'external' -%> and install - SonarQube <%= release.getVersion() -%> after having carefully read the <a href="http://docs.codehaus.org/display/SONAR/Upgrading" class="external">upgrade guide</a>. + <li> + Replace current version of plugin <%= plugin_to_upgrade.getArtifact().getName() -%> by + version <%= plugin_to_upgrade.getVersion() -%> + </form> </li> - <li>Start Sonar</li> + <% end %> + <li>Start SonarQube</li> </ol> <% else %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/users/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/users/index.html.erb index 2fd4e5045eb..131c2491982 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/users/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/users/index.html.erb @@ -20,11 +20,11 @@ <table class="data width100 sortable" id="users"> <thead> <tr> - <th class="left">Login</th> - <th class="left sortfirstasc">Name</th> - <th class="left">Email</th> - <th class="left nosort">Groups</th> - <th class="right nosort" nowrap>Operations</th> + <th class="left"><a>Login</a></th> + <th class="left sortfirstasc"><a>Name</a></th> + <th class="left"><a>Email</a></th> + <th class="left nosort"><a>Groups</a></th> + <th class="right nosort" nowrap><a>Operations</a></th> </tr> </thead> <tbody > diff --git a/server/sonar-web/src/main/webapp/fonts/sonar.eot b/server/sonar-web/src/main/webapp/fonts/sonar.eot Binary files differindex ba3b9187a7f..6ca49648665 100755 --- a/server/sonar-web/src/main/webapp/fonts/sonar.eot +++ b/server/sonar-web/src/main/webapp/fonts/sonar.eot diff --git a/server/sonar-web/src/main/webapp/fonts/sonar.svg b/server/sonar-web/src/main/webapp/fonts/sonar.svg index 562f4b05bd8..9383951117a 100755 --- a/server/sonar-web/src/main/webapp/fonts/sonar.svg +++ b/server/sonar-web/src/main/webapp/fonts/sonar.svg @@ -67,7 +67,6 @@ <glyph unicode="" d="M585.143 265.143q0-14.857-10.857-25.714t-25.714-10.857h-512q-14.857 0-25.714 10.857t-10.857 25.714 10.857 25.714l256 256q10.857 10.857 25.714 10.857t25.714-10.857l256-256q10.857-10.857 10.857-25.714z" horiz-adv-x="585" /> <glyph unicode="" d="M365.714 704v-512q0-14.857-10.857-25.714t-25.714-10.857-25.714 10.857l-256 256q-10.857 10.857-10.857 25.714t10.857 25.714l256 256q10.857 10.857 25.714 10.857t25.714-10.857 10.857-25.714z" horiz-adv-x="366" /> <glyph unicode="" d="M329.143 448q0-14.857-10.857-25.714l-256-256q-10.857-10.857-25.714-10.857t-25.714 10.857-10.857 25.714v512q0 14.857 10.857 25.714t25.714 10.857 25.714-10.857l256-256q10.857-10.857 10.857-25.714z" horiz-adv-x="366" /> -<glyph unicode="" d="M1024 283.429v-109.714q0-7.429-5.429-12.857t-12.857-5.429h-786.286v-109.714q0-7.429-5.429-12.857t-12.857-5.429q-6.857 0-13.714 5.714l-182.286 182.857q-5.143 5.143-5.143 12.571 0 8 5.143 13.143l182.857 182.857q5.143 5.143 13.143 5.143 7.429 0 12.857-5.429t5.429-12.857v-109.714h786.286q7.429 0 12.857-5.429t5.429-12.857zM1024 594.286q0-8-5.143-13.143l-182.857-182.857q-5.143-5.143-13.143-5.143-7.429 0-12.857 5.429t-5.429 12.857v109.714h-786.286q-7.429 0-12.857 5.429t-5.429 12.857v109.714q0 7.429 5.429 12.857t12.857 5.429h786.286v109.714q0 8 5.143 13.143t13.143 5.143q6.857 0 13.714-5.714l182.286-182.286q5.143-5.143 5.143-13.143z" /> <glyph unicode="" d="M731.643 283.429v-36.57c0-5.334-1.714-9.715-5.143-13.144s-7.81-5.143-13.143-5.143h-402.286c-5.333 0-9.714 1.714-13.143 5.143s-5.143 7.81-5.143 13.144v36.57c0 5.334 1.714 9.715 5.143 13.144s7.81 5.143 13.143 5.143h402.287c5.333 0 9.714-1.714 13.143-5.143s5.142-7.81 5.142-13.144zM731.643 429.714v-36.571c0-5.333-1.714-9.714-5.143-13.143s-7.81-5.143-13.143-5.143h-402.286c-5.333 0-9.714 1.714-13.143 5.143s-5.143 7.81-5.143 13.143v36.571c0 5.333 1.714 9.715 5.143 13.144s7.81 5.143 13.143 5.143h402.287c5.333 0 9.714-1.715 13.143-5.143s5.142-7.811 5.142-13.144zM219.643 82.286h585.143v438.857h-237.714c-15.238 0-28.19 5.334-38.857 16s-16 23.619-16 38.857v237.714h-292.571l-0.001-731.428zM585.357 594.286h214.856c-3.81 11.047-7.999 18.857-12.571 23.429l-178.856 178.857c-4.571 4.571-12.381 8.761-23.429 12.571v-214.857zM877.929 576v-512c0-15.238-5.333-28.19-16-38.857s-23.618-16-38.856-16h-621.715c-15.238 0-28.19 5.334-38.857 16s-16 23.619-16 38.857v768c0 15.238 5.333 28.19 16 38.857s23.619 16 38.857 16h365.714c15.237 0 32-3.81 50.286-11.429s32.762-16.762 43.429-27.429l178.286-178.286c10.667-10.667 19.81-25.143 27.429-43.429s11.429-35.048 11.429-50.286l-0.002 0.002z" horiz-adv-x="1019" /> <glyph unicode="" d="M358.286 173.714q0-7.429-5.714-13.143l-28.571-28.571q-5.714-5.714-13.143-5.714t-13.143 5.714l-266.286 266.286q-5.714 5.714-5.714 13.143t5.714 13.143l266.286 266.286q5.714 5.714 13.143 5.714t13.143-5.714l28.571-28.571q5.714-5.714 5.714-13.143t-5.714-13.143l-224.571-224.571 224.571-224.571q5.714-5.714 5.714-13.143zM577.714 173.714q0-7.429-5.714-13.143l-28.571-28.571q-5.714-5.714-13.143-5.714t-13.143 5.714l-266.286 266.286q-5.714 5.714-5.714 13.143t5.714 13.143l266.286 266.286q5.714 5.714 13.143 5.714t13.143-5.714l28.571-28.571q5.714-5.714 5.714-13.143t-5.714-13.143l-224.571-224.571 224.571-224.571q5.714-5.714 5.714-13.143z" horiz-adv-x="585" /> <glyph unicode="" d="M340 411.429q0-7.429-5.714-13.143l-266.286-266.286q-5.714-5.714-13.143-5.714t-13.143 5.714l-28.571 28.571q-5.714 5.714-5.714 13.143t5.714 13.143l224.571 224.571-224.571 224.571q-5.714 5.714-5.714 13.143t5.714 13.143l28.571 28.571q5.714 5.714 13.143 5.714t13.143-5.714l266.286-266.286q5.714-5.714 5.714-13.143zM559.429 411.429q0-7.429-5.714-13.143l-266.286-266.286q-5.714-5.714-13.143-5.714t-13.143 5.714l-28.571 28.571q-5.714 5.714-5.714 13.143t5.714 13.143l224.571 224.571-224.571 224.571q-5.714 5.714-5.714 13.143t5.714 13.143l28.571 28.571q5.714 5.714 13.143 5.714t13.143-5.714l266.286-266.286q5.714-5.714 5.714-13.143z" horiz-adv-x="585" /> diff --git a/server/sonar-web/src/main/webapp/fonts/sonar.ttf b/server/sonar-web/src/main/webapp/fonts/sonar.ttf Binary files differindex cf9bd8fb9a2..6d672598d25 100755 --- a/server/sonar-web/src/main/webapp/fonts/sonar.ttf +++ b/server/sonar-web/src/main/webapp/fonts/sonar.ttf diff --git a/server/sonar-web/src/main/webapp/fonts/sonar.woff b/server/sonar-web/src/main/webapp/fonts/sonar.woff Binary files differindex b32c6ddbdba..dd58a26a975 100755 --- a/server/sonar-web/src/main/webapp/fonts/sonar.woff +++ b/server/sonar-web/src/main/webapp/fonts/sonar.woff diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ActiveRule.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ActiveRule.java index 6c1992ac1e2..4fda09f8910 100644 --- a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ActiveRule.java +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ActiveRule.java @@ -20,6 +20,7 @@ package org.sonar.batch.protocol.input; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; @@ -29,7 +30,7 @@ public class ActiveRule { private final String name, severity, internalKey, language; private final Map<String, String> params = new HashMap<String, String>(); - public ActiveRule(String repositoryKey, String ruleKey, String name, String severity, String internalKey, String language) { + public ActiveRule(String repositoryKey, String ruleKey, String name, String severity, @Nullable String internalKey, String language) { this.repositoryKey = repositoryKey; this.ruleKey = ruleKey; this.name = name; @@ -72,6 +73,7 @@ public class ActiveRule { return params; } + @CheckForNull public String internalKey() { return internalKey; } diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/Metric.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/Metric.java index f09f3b34e81..775acb52523 100644 --- a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/Metric.java +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/Metric.java @@ -49,7 +49,7 @@ public class Metric { public Metric(int id, String key, String valueType, - String description, + @Nullable String description, int direction, String name, boolean qualitative, @@ -82,6 +82,7 @@ public class Metric { return valueType; } + @CheckForNull public String description() { return description; } diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java index a386a0eeac7..570fb3630e5 100644 --- a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java +++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/input/ProjectReferentials.java @@ -43,7 +43,12 @@ public class ProjectReferentials { } public ProjectReferentials addSettings(String projectKey, Map<String, String> settings) { - settingsByModule.put(projectKey, settings); + Map<String, String> existingSettings = settingsByModule.get(projectKey); + if (existingSettings == null) { + existingSettings = new HashMap<String, String>(); + } + existingSettings.putAll(settings); + settingsByModule.put(projectKey, existingSettings); return this; } diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml index 1ba3a32d61f..89f7c9252a4 100644 --- a/sonar-batch/pom.xml +++ b/sonar-batch/pom.xml @@ -149,5 +149,10 @@ <artifactId>jsonassert</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.codehaus.sonar.plugins</groupId> + <artifactId>sonar-xoo-plugin</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java index 7317c9afd08..32bebf6182e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java @@ -22,6 +22,7 @@ package org.sonar.batch.bootstrap; import com.google.common.collect.Lists; import org.apache.commons.lang.ClassUtils; import org.sonar.api.batch.CheckProject; +import org.sonar.api.batch.Phase; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.platform.ComponentContainer; @@ -56,6 +57,15 @@ public class BatchExtensionDictionnary extends org.sonar.api.batch.BatchExtensio return result; } + @Override + protected Phase.Name evaluatePhase(Object extension) { + if (extension instanceof SensorWrapper) { + return super.evaluatePhase(((SensorWrapper) extension).wrappedSensor()); + } else { + return super.evaluatePhase(extension); + } + } + private <T> List<T> getFilteredExtensions(Class<T> type, @Nullable Project project, @Nullable ExtensionMatcher matcher) { List<T> result = Lists.newArrayList(); for (Object extension : getExtensions(type)) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java new file mode 100644 index 00000000000..f6a4e3d18bf --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java @@ -0,0 +1,56 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import org.sonar.api.BatchComponent; +import org.sonar.batch.index.Cache; +import org.sonar.batch.index.Cache.Entry; +import org.sonar.batch.index.Caches; +import org.sonar.duplications.block.FileBlocks; + +import javax.annotation.CheckForNull; + +/** + * Cache of duplication blocks. This cache is shared amongst all project modules. + */ +public class BlockCache implements BatchComponent { + + private final Cache<FileBlocks> cache; + + public BlockCache(Caches caches) { + caches.registerValueCoder(FileBlocks.class, new FileBlocksValueCoder()); + cache = caches.createCache("blocks"); + } + + public Iterable<Entry<FileBlocks>> entries() { + return cache.entries(); + } + + @CheckForNull + public FileBlocks byComponent(String effectiveKey) { + return cache.get(effectiveKey); + } + + public BlockCache put(String effectiveKey, FileBlocks blocks) { + cache.put(effectiveKey, blocks); + return this; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java new file mode 100644 index 00000000000..d6dd18026a2 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java @@ -0,0 +1,74 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import com.google.common.base.Preconditions; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.duplication.DuplicationBuilder; + +import java.util.ArrayList; + +public class DefaultDuplicationBuilder implements DuplicationBuilder { + + private final InputFile inputFile; + private final DuplicationCache duplicationCache; + private boolean done = false; + private DuplicationGroup current = null; + private ArrayList<DuplicationGroup> duplications; + + public DefaultDuplicationBuilder(InputFile inputFile, DuplicationCache duplicationCache) { + this.inputFile = inputFile; + this.duplicationCache = duplicationCache; + duplications = new ArrayList<DuplicationGroup>(); + } + + @Override + public DuplicationBuilder originBlock(int startLine, int endLine) { + if (current != null) { + duplications.add(current); + } + current = new DuplicationGroup(new DuplicationGroup.Block(((DefaultInputFile) inputFile).key(), startLine, endLine - startLine + 1)); + return this; + } + + @Override + public DuplicationBuilder isDuplicatedBy(InputFile sameOrOtherFile, int startLine, int endLine) { + return isDuplicatedBy(((DefaultInputFile) sameOrOtherFile).key(), startLine, endLine); + } + + /** + * For internal use. Global duplications are referencing files outside of current project so + * no way to manipulate an InputFile. + */ + public DuplicationBuilder isDuplicatedBy(String fileKey, int startLine, int endLine) { + Preconditions.checkNotNull(current, "Call originBlock() first"); + current.addDuplicate(new DuplicationGroup.Block(fileKey, startLine, endLine - startLine + 1)); + return this; + } + + @Override + public void done() { + Preconditions.checkState(!done, "done() already called"); + Preconditions.checkNotNull(current, "Call originBlock() first"); + duplications.add(current); + duplicationCache.put(((DefaultInputFile) inputFile).key(), duplications); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java new file mode 100644 index 00000000000..6c7836d9e39 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java @@ -0,0 +1,77 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import com.google.common.base.Preconditions; +import net.sourceforge.pmd.cpd.TokenEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.duplication.TokenBuilder; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.FileBlocks; +import org.sonar.duplications.internal.pmd.PmdBlockChunker; +import org.sonar.duplications.internal.pmd.TokenizerBridge; +import org.sonar.duplications.internal.pmd.TokensLine; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultTokenBuilder implements TokenBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenBuilder.class); + + private final BlockCache cache; + private final InputFile inputFile; + private final List<TokenEntry> tokens = new ArrayList<TokenEntry>(); + private final PmdBlockChunker blockChunker; + private boolean done = false; + private int previousLine = 0; + + public DefaultTokenBuilder(InputFile inputFile, BlockCache cache, PmdBlockChunker blockChunker) { + this.inputFile = inputFile; + this.cache = cache; + this.blockChunker = blockChunker; + TokenEntry.clearImages(); + } + + @Override + public DefaultTokenBuilder addToken(int line, String image) { + Preconditions.checkState(!done, "done() already called"); + Preconditions.checkState(line >= previousLine, "Token should be created in order. Previous line was " + previousLine + " and you tried to create a token at line " + line); + TokenEntry cpdToken = new TokenEntry(image, inputFile.absolutePath(), line); + tokens.add(cpdToken); + previousLine = line; + return this; + } + + @Override + public void done() { + Preconditions.checkState(!done, "done() already called"); + tokens.add(TokenEntry.getEOF()); + TokenEntry.clearImages(); + List<TokensLine> tokensLines = TokenizerBridge.convert(tokens); + ArrayList<Block> blocks = blockChunker.chunk(((DefaultInputFile) inputFile).key(), tokensLines); + + cache.put(((DefaultInputFile) inputFile).key(), new FileBlocks(((DefaultInputFile) inputFile).key(), blocks)); + tokens.clear(); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java new file mode 100644 index 00000000000..ab1dad47009 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java @@ -0,0 +1,43 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; + +class DuplicationBlockValueCoder implements ValueCoder { + + @Override + public void put(Value value, Object object, CoderContext context) { + DuplicationGroup.Block b = (DuplicationGroup.Block) object; + value.putUTF(b.resourceKey()); + value.put(b.startLine()); + value.put(b.length()); + } + + @Override + public Object get(Value value, Class clazz, CoderContext context) { + String resourceKey = value.getString(); + int startLine = value.getInt(); + int length = value.getInt(); + return new DuplicationGroup.Block(resourceKey, startLine, length); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java new file mode 100644 index 00000000000..e0265215efc --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import org.sonar.api.BatchComponent; +import org.sonar.batch.index.Cache; +import org.sonar.batch.index.Cache.Entry; +import org.sonar.batch.index.Caches; + +import javax.annotation.CheckForNull; + +import java.util.ArrayList; + +/** + * Cache of duplication blocks. This cache is shared amongst all project modules. + */ +public class DuplicationCache implements BatchComponent { + + private final Cache<ArrayList<DuplicationGroup>> cache; + + public DuplicationCache(Caches caches) { + caches.registerValueCoder(DuplicationGroup.class, new DuplicationGroupValueCoder()); + caches.registerValueCoder(DuplicationGroup.Block.class, new DuplicationBlockValueCoder()); + cache = caches.createCache("duplications"); + } + + public Iterable<Entry<ArrayList<DuplicationGroup>>> entries() { + return cache.entries(); + } + + @CheckForNull + public ArrayList<DuplicationGroup> byComponent(String effectiveKey) { + return cache.get(effectiveKey); + } + + public DuplicationCache put(String effectiveKey, ArrayList<DuplicationGroup> blocks) { + cache.put(effectiveKey, blocks); + return this; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java new file mode 100644 index 00000000000..dc9a7aa2602 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java @@ -0,0 +1,76 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import java.util.ArrayList; +import java.util.List; + +public class DuplicationGroup { + + public static class Block { + private final String resourceKey; + private final int startLine; + private final int length; + + public Block(String resourceKey, int startLine, int length) { + this.resourceKey = resourceKey; + this.startLine = startLine; + this.length = length; + } + + public String resourceKey() { + return resourceKey; + } + + public int startLine() { + return startLine; + } + + public int length() { + return length; + } + } + + private final Block originBlock; + + private List<Block> duplicates = new ArrayList<DuplicationGroup.Block>(); + + public DuplicationGroup(Block originBlock) { + this.originBlock = originBlock; + } + + public void setDuplicates(List<Block> duplicates) { + this.duplicates = duplicates; + } + + public DuplicationGroup addDuplicate(Block anotherBlock) { + this.duplicates.add(anotherBlock); + return this; + } + + public Block originBlock() { + return originBlock; + } + + public List<Block> duplicates() { + return duplicates; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java new file mode 100644 index 00000000000..244cffccd5f --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java @@ -0,0 +1,54 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; +import org.sonar.batch.duplication.DuplicationGroup.Block; + +import java.util.ArrayList; + +class DuplicationGroupValueCoder implements ValueCoder { + + private DuplicationBlockValueCoder blockCoder = new DuplicationBlockValueCoder(); + + @Override + public void put(Value value, Object object, CoderContext context) { + DuplicationGroup c = (DuplicationGroup) object; + value.put(c.originBlock()); + value.put(c.duplicates().size()); + for (DuplicationGroup.Block block : c.duplicates()) { + blockCoder.put(value, block, context); + } + } + + @Override + public Object get(Value value, Class clazz, CoderContext context) { + DuplicationGroup g = new DuplicationGroup((DuplicationGroup.Block) value.get()); + int count = value.getInt(); + ArrayList<DuplicationGroup.Block> blocks = new ArrayList<DuplicationGroup.Block>(count); + for (int i = 0; i < count; i++) { + blocks.add((Block) blockCoder.get(value, DuplicationGroup.Block.class, context)); + } + g.setDuplicates(blocks); + return g; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java new file mode 100644 index 00000000000..99b3467c921 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java @@ -0,0 +1,69 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; +import org.sonar.duplications.block.FileBlocks; + +import java.util.ArrayList; +import java.util.List; + +class FileBlocksValueCoder implements ValueCoder { + + @Override + public void put(Value value, Object object, CoderContext context) { + FileBlocks blocks = (FileBlocks) object; + value.putUTF(blocks.resourceId()); + value.put(blocks.blocks().size()); + for (Block b : blocks.blocks()) { + value.putByteArray(b.getBlockHash().getBytes()); + value.put(b.getIndexInFile()); + value.put(b.getStartLine()); + value.put(b.getEndLine()); + value.put(b.getStartUnit()); + value.put(b.getEndUnit()); + } + } + + @Override + public Object get(Value value, Class clazz, CoderContext context) { + String resourceId = value.getString(); + int count = value.getInt(); + List<Block> blocks = new ArrayList<Block>(count); + for (int i = 0; i < count; i++) { + Block.Builder b = Block.builder(); + b.setResourceId(resourceId); + b.setBlockHash(new ByteArray(value.getByteArray())); + b.setIndexInFile(value.getInt()); + int startLine = value.getInt(); + int endLine = value.getInt(); + b.setLines(startLine, endLine); + int startUnit = value.getInt(); + int endUnit = value.getInt(); + b.setUnit(startUnit, endUnit); + blocks.add(b.build()); + } + return new FileBlocks(resourceId, blocks); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/Xoo.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/package-info.java index 1922fd7aa35..103e90ab281 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/Xoo.java +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/package-info.java @@ -17,24 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.mediumtest.xoo.plugin.base; +@ParametersAreNonnullByDefault +package org.sonar.batch.duplication; -import org.sonar.api.resources.AbstractLanguage; - -public class Xoo extends AbstractLanguage { - - public static final String KEY = "xoo"; - public static final String NAME = "Xoo"; - - public Xoo() { - super(KEY, NAME); - } - - /** - * ${@inheritDoc} - */ - public String[] getFileSuffixes() { - return XooConstants.FILE_SUFFIXES; - } - -} +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java index 38ae875af98..3a74dbf4884 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java @@ -32,9 +32,6 @@ import java.util.List; import static com.google.common.collect.Lists.newArrayList; -/** - * @since 3.6 - */ public class SyntaxHighlightingDataBuilder { private List<SyntaxHighlightingRule> syntaxHighlightingRuleSet; diff --git a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java index 08985752d60..e6faa7651b4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java @@ -21,9 +21,6 @@ package org.sonar.batch.highlighting; import java.io.Serializable; -/** - * @since 3.6 - */ public class SyntaxHighlightingRule implements Serializable { private final int startPosition; diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java index ff57e4c065d..c98b88773e6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java @@ -28,7 +28,6 @@ import org.apache.commons.lang.builder.ToStringBuilder; import javax.annotation.CheckForNull; -import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; @@ -38,7 +37,7 @@ import java.util.Set; * This cache is not thread-safe, due to direct usage of {@link com.persistit.Exchange} * </p> */ -public class Cache<V extends Serializable> { +public class Cache<V> { private final String name; private final Exchange exchange; @@ -383,7 +382,7 @@ public class Cache<V extends Serializable> { // LAZY ITERATORS AND ITERABLES // - private static class ValueIterable<T extends Serializable> implements Iterable<T> { + private static class ValueIterable<T> implements Iterable<T> { private final Iterator<T> iterator; private ValueIterable(Exchange exchange, KeyFilter keyFilter) { @@ -396,7 +395,7 @@ public class Cache<V extends Serializable> { } } - private static class ValueIterator<T extends Serializable> implements Iterator<T> { + private static class ValueIterator<T> implements Iterator<T> { private final Exchange exchange; private final KeyFilter keyFilter; @@ -434,7 +433,7 @@ public class Cache<V extends Serializable> { } } - private static class EntryIterable<T extends Serializable> implements Iterable<Entry<T>> { + private static class EntryIterable<T> implements Iterable<Entry<T>> { private final EntryIterator<T> it; private EntryIterable(Exchange exchange, KeyFilter keyFilter) { @@ -447,7 +446,7 @@ public class Cache<V extends Serializable> { } } - private static class EntryIterator<T extends Serializable> implements Iterator<Entry<T>> { + private static class EntryIterator<T> implements Iterator<Entry<T>> { private final Exchange exchange; private final KeyFilter keyFilter; @@ -491,7 +490,7 @@ public class Cache<V extends Serializable> { } } - public static class Entry<V extends Serializable> { + public static class Entry<V> { private final Object[] key; private final V value; diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java b/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java index 1e8c53d8853..ca2a33a5014 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java @@ -36,7 +36,6 @@ import org.sonar.api.BatchComponent; import org.sonar.api.utils.TempFolder; import java.io.File; -import java.io.Serializable; import java.util.Properties; import java.util.Set; @@ -85,7 +84,7 @@ public class Caches implements BatchComponent, Startable { cm.registerValueCoder(clazz, coder); } - public <V extends Serializable> Cache<V> createCache(String cacheName) { + public <V> Cache<V> createCache(String cacheName) { Preconditions.checkState(volume != null && volume.isOpened(), "Caches are not initialized"); Preconditions.checkState(!cacheNames.contains(cacheName), "Cache is already created: " + cacheName); try { diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java index 2a63d4b8b9e..0f7691728a1 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java @@ -20,10 +20,13 @@ package org.sonar.batch.index; import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang.StringEscapeUtils; import org.sonar.api.database.model.MeasureMapper; import org.sonar.api.database.model.MeasureModel; import org.sonar.api.database.model.Snapshot; +import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; +import org.sonar.api.measures.PersistenceMode; import org.sonar.api.measures.RuleMeasure; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; @@ -31,6 +34,8 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.technicaldebt.batch.Characteristic; +import org.sonar.batch.duplication.DuplicationCache; +import org.sonar.batch.duplication.DuplicationGroup; import org.sonar.batch.index.Cache.Entry; import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.core.persistence.DbSession; @@ -38,20 +43,27 @@ import org.sonar.core.persistence.MyBatis; import javax.annotation.Nullable; +import java.util.ArrayList; + public final class MeasurePersister implements ScanPersister { private final MyBatis mybatis; private final RuleFinder ruleFinder; private final MeasureCache measureCache; private final SnapshotCache snapshotCache; private final ResourceCache resourceCache; + private final DuplicationCache duplicationCache; + private final org.sonar.api.measures.MetricFinder metricFinder; public MeasurePersister(MyBatis mybatis, RuleFinder ruleFinder, - MeasureCache measureCache, SnapshotCache snapshotCache, ResourceCache resourceCache) { + MeasureCache measureCache, SnapshotCache snapshotCache, ResourceCache resourceCache, + DuplicationCache duplicationCache, org.sonar.api.measures.MetricFinder metricFinder) { this.mybatis = mybatis; this.ruleFinder = ruleFinder; this.measureCache = measureCache; this.snapshotCache = snapshotCache; this.resourceCache = resourceCache; + this.duplicationCache = duplicationCache; + this.metricFinder = metricFinder; } @Override @@ -72,6 +84,19 @@ public final class MeasurePersister implements ScanPersister { } } + org.sonar.api.measures.Metric duplicationMetricWithId = metricFinder.findByKey(CoreMetrics.DUPLICATIONS_DATA_KEY); + for (Entry<ArrayList<DuplicationGroup>> entry : duplicationCache.entries()) { + String effectiveKey = entry.key()[0].toString(); + Measure measure = new Measure(duplicationMetricWithId, toXml(entry.value())).setPersistenceMode(PersistenceMode.DATABASE); + Resource resource = resourceCache.get(effectiveKey); + + if (shouldPersistMeasure(resource, measure)) { + Snapshot snapshot = snapshotCache.get(effectiveKey); + MeasureModel measureModel = model(measure).setSnapshotId(snapshot.getId()); + mapper.insert(measureModel); + } + } + session.commit(); } catch (Exception e) { throw new IllegalStateException("Unable to save some measures", e); @@ -80,6 +105,28 @@ public final class MeasurePersister implements ScanPersister { } } + private static String toXml(Iterable<DuplicationGroup> duplications) { + StringBuilder xml = new StringBuilder(); + xml.append("<duplications>"); + for (DuplicationGroup duplication : duplications) { + xml.append("<g>"); + toXml(xml, duplication.originBlock()); + for (DuplicationGroup.Block part : duplication.duplicates()) { + toXml(xml, part); + } + xml.append("</g>"); + } + xml.append("</duplications>"); + return xml.toString(); + } + + private static void toXml(StringBuilder xml, DuplicationGroup.Block part) { + xml.append("<b s=\"").append(part.startLine()) + .append("\" l=\"").append(part.length()) + .append("\" r=\"").append(StringEscapeUtils.escapeXml(part.resourceKey())) + .append("\"/>"); + } + @VisibleForTesting static boolean shouldPersistMeasure(@Nullable Resource resource, @Nullable Measure measure) { if (resource == null || measure == null) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index fc7a89ebad8..58cc4f461af 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -41,8 +41,11 @@ import org.sonar.api.resources.Languages; import org.sonar.batch.bootstrap.PluginsReferential; import org.sonar.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.batch.duplication.DuplicationCache; +import org.sonar.batch.duplication.DuplicationGroup; import org.sonar.batch.highlighting.SyntaxHighlightingData; import org.sonar.batch.highlighting.SyntaxHighlightingRule; +import org.sonar.batch.index.Cache.Entry; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.protocol.input.ActiveRule; import org.sonar.batch.protocol.input.GlobalReferentials; @@ -222,6 +225,7 @@ public class BatchMediumTester { private List<Issue> issues = new ArrayList<Issue>(); private List<Measure> measures = new ArrayList<Measure>(); + private Map<String, List<DuplicationGroup>> duplications = new HashMap<String, List<DuplicationGroup>>(); private List<InputFile> inputFiles = new ArrayList<InputFile>(); private List<InputDir> inputDirs = new ArrayList<InputDir>(); private Map<InputFile, SyntaxHighlightingData> highlightingPerFile = new HashMap<InputFile, SyntaxHighlightingData>(); @@ -259,6 +263,12 @@ public class BatchMediumTester { } } + DuplicationCache duplicationCache = container.getComponentByType(DuplicationCache.class); + for (Entry<ArrayList<DuplicationGroup>> entry : duplicationCache.entries()) { + String effectiveKey = entry.key()[0].toString(); + duplications.put(effectiveKey, entry.value()); + } + } public List<Issue> issues() { @@ -277,6 +287,10 @@ public class BatchMediumTester { return inputDirs; } + public List<DuplicationGroup> duplicationsFor(InputFile inputFile) { + return duplications.get(((DefaultInputFile) inputFile).key()); + } + /** * Get highlighting type at a given position in an inputfile * @param charIndex 0-based offset in file diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java b/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java index 75a063eb119..ac08e40f5fa 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java @@ -20,14 +20,24 @@ package org.sonar.batch.referential; import org.picocontainer.injectors.ProviderAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.Settings; import org.sonar.api.resources.Languages; +import org.sonar.api.utils.TimeProfiler; import org.sonar.batch.protocol.input.ProjectReferentials; public class ProjectReferentialsProvider extends ProviderAdapter { + private static final Logger LOG = LoggerFactory.getLogger(ProjectReferentialsProvider.class); + public ProjectReferentials provide(ProjectReferentialsLoader loader, ProjectReactor reactor, Settings settings, Languages languages) { - return loader.load(reactor, settings, languages); + TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials"); + try { + return loader.load(reactor, settings, languages); + } finally { + profiler.stop(); + } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index c5e21b89a66..3a209b91961 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -42,6 +42,8 @@ import org.sonar.batch.bootstrap.MetricProvider; import org.sonar.batch.components.PeriodsDefinition; import org.sonar.batch.debt.DebtModelProvider; import org.sonar.batch.debt.IssueChangelogDebtCalculator; +import org.sonar.batch.duplication.BlockCache; +import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.Caches; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.index.ComponentDataPersister; @@ -194,6 +196,10 @@ public class ProjectScanContainer extends ComponentContainer { new RulesProvider(), new DebtModelProvider(), + // Duplications + BlockCache.class, + DuplicationCache.class, + ProjectSettings.class); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java index 1de8fd7201c..d285a6c8646 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java @@ -27,6 +27,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.duplication.DuplicationBuilder; +import org.sonar.api.batch.sensor.duplication.TokenBuilder; import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; @@ -49,9 +51,14 @@ import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.Scopes; import org.sonar.api.rule.RuleKey; +import org.sonar.batch.duplication.BlockCache; +import org.sonar.batch.duplication.DefaultDuplicationBuilder; +import org.sonar.batch.duplication.DefaultTokenBuilder; +import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.highlighting.DefaultHighlightingBuilder; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.symbol.DefaultSymbolTableBuilder; +import org.sonar.duplications.internal.pmd.PmdBlockChunker; import java.io.Serializable; @@ -61,17 +68,20 @@ import java.io.Serializable; */ public class SensorContextAdaptor implements SensorContext { - private org.sonar.api.batch.SensorContext sensorContext; - private MetricFinder metricFinder; - private Project project; - private ResourcePerspectives perspectives; - private Settings settings; - private FileSystem fs; - private ActiveRules activeRules; - private ComponentDataCache componentDataCache; + private final org.sonar.api.batch.SensorContext sensorContext; + private final MetricFinder metricFinder; + private final Project project; + private final ResourcePerspectives perspectives; + private final Settings settings; + private final FileSystem fs; + private final ActiveRules activeRules; + private final ComponentDataCache componentDataCache; + private final BlockCache blockCache; + private final DuplicationCache duplicationCache; public SensorContextAdaptor(org.sonar.api.batch.SensorContext sensorContext, MetricFinder metricFinder, Project project, ResourcePerspectives perspectives, - Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache) { + Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache, BlockCache blockCache, + DuplicationCache duplicationCache) { this.sensorContext = sensorContext; this.metricFinder = metricFinder; this.project = project; @@ -80,6 +90,8 @@ public class SensorContextAdaptor implements SensorContext { this.fs = fs; this.activeRules = activeRules; this.componentDataCache = componentDataCache; + this.blockCache = blockCache; + this.duplicationCache = duplicationCache; } @Override @@ -254,4 +266,33 @@ public class SensorContextAdaptor implements SensorContext { return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache); } + @Override + public TokenBuilder tokenBuilder(InputFile inputFile) { + PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language())); + return new DefaultTokenBuilder(inputFile, blockCache, blockChunker); + } + + @Override + public DuplicationBuilder duplicationBuilder(InputFile inputFile) { + return new DefaultDuplicationBuilder(inputFile, duplicationCache); + } + + private int getBlockSize(String languageKey) { + int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); + if (blockSize == 0) { + blockSize = getDefaultBlockSize(languageKey); + } + return blockSize; + } + + private static int getDefaultBlockSize(String languageKey) { + if ("cobol".equals(languageKey)) { + return 30; + } else if ("abap".equals(languageKey) || "natur".equals(languageKey)) { + return 20; + } else { + return 10; + } + } + } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java index b5929008269..32a49a16492 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java @@ -33,19 +33,23 @@ import java.util.List; public class SensorWrapper implements org.sonar.api.batch.Sensor { - private Sensor analyzer; + private Sensor wrappedSensor; private SensorContext adaptor; private DefaultSensorDescriptor descriptor; private AnalyzerOptimizer optimizer; public SensorWrapper(Sensor newSensor, SensorContext adaptor, AnalyzerOptimizer optimizer) { - this.analyzer = newSensor; + this.wrappedSensor = newSensor; this.optimizer = optimizer; descriptor = new DefaultSensorDescriptor(); newSensor.describe(descriptor); this.adaptor = adaptor; } + public Sensor wrappedSensor() { + return wrappedSensor; + } + @DependedUpon public List<Metric> provides() { return Arrays.asList(descriptor.provides()); @@ -63,7 +67,7 @@ public class SensorWrapper implements org.sonar.api.batch.Sensor { @Override public void analyse(Project module, org.sonar.api.batch.SensorContext context) { - analyzer.execute(adaptor); + wrappedSensor.execute(adaptor); } @Override diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java new file mode 100644 index 00000000000..6dcfc9903b1 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java @@ -0,0 +1,76 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; + +import javax.annotation.Nullable; + +import java.io.File; + +class DefaultInputFileValueCoder implements ValueCoder { + + @Override + public void put(Value value, Object object, CoderContext context) { + DeprecatedDefaultInputFile f = (DeprecatedDefaultInputFile) object; + putUTFOrNull(value, f.relativePath()); + value.putString(f.getFileBaseDir().toString()); + putUTFOrNull(value, f.deprecatedKey()); + value.putString(f.sourceDirAbsolutePath()); + putUTFOrNull(value, f.pathRelativeToSourceDir()); + putUTFOrNull(value, f.absolutePath()); + value.putString(f.language()); + value.putString(f.type().name()); + value.putString(f.status().name()); + putUTFOrNull(value, f.hash()); + value.put(f.lines()); + putUTFOrNull(value, f.key()); + } + + private void putUTFOrNull(Value value, @Nullable String utfOrNull) { + if (utfOrNull != null) { + value.putUTF(utfOrNull); + } else { + value.putNull(); + } + } + + @Override + public Object get(Value value, Class clazz, CoderContext context) { + DeprecatedDefaultInputFile file = new DeprecatedDefaultInputFile(value.getString()); + file.setBasedir(new File(value.getString())); + file.setDeprecatedKey(value.getString()); + file.setSourceDirAbsolutePath(value.getString()); + file.setPathRelativeToSourceDir(value.getString()); + file.setAbsolutePath(value.getString()); + file.setLanguage(value.getString()); + file.setType(InputFile.Type.valueOf(value.getString())); + file.setStatus(InputFile.Status.valueOf(value.getString())); + file.setHash(value.getString()); + file.setLines(value.getInt()); + file.setKey(value.getString()); + return file; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java index 5d901a517ae..39672d7f7c5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java @@ -23,6 +23,7 @@ import org.sonar.api.BatchComponent; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; import org.sonar.batch.index.Cache; import org.sonar.batch.index.Caches; @@ -44,6 +45,7 @@ public class InputPathCache implements BatchComponent { private final Cache<InputPath> cache; public InputPathCache(Caches caches) { + caches.registerValueCoder(DeprecatedDefaultInputFile.class, new DefaultInputFileValueCoder()); cache = caches.createCache("inputFiles"); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java index 7f010d97d0a..bd8cee930d8 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java @@ -30,6 +30,8 @@ import org.sonar.api.technicaldebt.batch.Characteristic; import org.sonar.api.technicaldebt.batch.Requirement; import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; +import javax.annotation.Nullable; + class MeasureValueCoder implements ValueCoder { private final MetricFinder metricFinder; @@ -42,12 +44,12 @@ class MeasureValueCoder implements ValueCoder { public void put(Value value, Object object, CoderContext context) { Measure<?> m = (Measure) object; - value.putString(m.getMetricKey()); + value.putUTF(m.getMetricKey()); value.put(m.getValue()); - value.putString(m.getData()); - value.putString(m.getDescription()); + putUTFOrNull(value, m.getData()); + putUTFOrNull(value, m.getDescription()); value.putString(m.getAlertStatus() != null ? m.getAlertStatus().name() : null); - value.putString(m.getAlertText()); + putUTFOrNull(value, m.getAlertText()); value.put(m.getTendency()); value.putDate(m.getDate()); value.put(m.getVariation1()); @@ -55,7 +57,7 @@ class MeasureValueCoder implements ValueCoder { value.put(m.getVariation3()); value.put(m.getVariation4()); value.put(m.getVariation5()); - value.putString(m.getUrl()); + putUTFOrNull(value, m.getUrl()); Characteristic characteristic = m.getCharacteristic(); value.put(characteristic != null ? characteristic.id() : null); Requirement requirement = m.getRequirement(); @@ -66,6 +68,14 @@ class MeasureValueCoder implements ValueCoder { value.putString(persistenceMode != null ? persistenceMode.name() : null); } + private void putUTFOrNull(Value value, @Nullable String utfOrNull) { + if (utfOrNull != null) { + value.putUTF(utfOrNull); + } else { + value.putNull(); + } + } + public Object get(Value value, Class clazz, CoderContext context) { Measure<?> m = new Measure(); String metricKey = value.getString(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java index 75d0e31932c..801744de2c7 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java @@ -19,10 +19,10 @@ */ package org.sonar.batch.scan2; -import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; - import com.google.common.base.Preconditions; import org.sonar.api.BatchComponent; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; import org.sonar.batch.index.Cache; import org.sonar.batch.index.Cache.Entry; import org.sonar.batch.index.Caches; @@ -35,7 +35,8 @@ public class AnalyzerMeasureCache implements BatchComponent { // project key -> component key -> metric key -> measure private final Cache<DefaultMeasure> cache; - public AnalyzerMeasureCache(Caches caches) { + public AnalyzerMeasureCache(Caches caches, MetricFinder metricFinder) { + caches.registerValueCoder(DefaultMeasure.class, new DefaultMeasureValueCoder(metricFinder)); cache = caches.createCache("measures"); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java new file mode 100644 index 00000000000..8b82a684dd0 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java @@ -0,0 +1,64 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan2; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder; + +import java.io.Serializable; + +class DefaultMeasureValueCoder implements ValueCoder { + + private MetricFinder metricFinder; + + public DefaultMeasureValueCoder(MetricFinder metricFinder) { + this.metricFinder = metricFinder; + } + + @Override + public void put(Value value, Object object, CoderContext context) { + DefaultMeasure m = (DefaultMeasure) object; + value.put(m.inputFile()); + value.putUTF(m.metric().key()); + value.put(m.value()); + } + + @Override + public Object get(Value value, Class clazz, CoderContext context) { + DefaultMeasureBuilder builder = new DefaultMeasureBuilder(); + InputFile f = (InputFile) value.get(); + if (f != null) { + builder.onFile(f); + } else { + builder.onProject(); + } + Metric m = metricFinder.findByKey(value.getString()); + builder.forMetric(m); + builder.withValue((Serializable) value.get()); + return builder.build(); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java index dcd6efd1ca4..73d6429bfcd 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java @@ -28,6 +28,8 @@ import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.rule.internal.DefaultActiveRule; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.duplication.DuplicationBuilder; +import org.sonar.api.batch.sensor.duplication.TokenBuilder; import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; @@ -41,12 +43,17 @@ import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; import org.sonar.api.config.Settings; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.MessageException; +import org.sonar.batch.duplication.BlockCache; +import org.sonar.batch.duplication.DefaultDuplicationBuilder; +import org.sonar.batch.duplication.DefaultTokenBuilder; +import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.highlighting.DefaultHighlightingBuilder; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.issue.IssueFilters; import org.sonar.batch.scan.SensorContextAdaptor; import org.sonar.batch.symbol.DefaultSymbolTableBuilder; import org.sonar.core.component.ComponentKeys; +import org.sonar.duplications.internal.pmd.PmdBlockChunker; import java.io.Serializable; @@ -60,9 +67,12 @@ public class DefaultSensorContext implements SensorContext { private final ActiveRules activeRules; private final IssueFilters issueFilters; private final ComponentDataCache componentDataCache; + private final BlockCache blockCache; + private final DuplicationCache duplicationCache; public DefaultSensorContext(ProjectDefinition def, AnalyzerMeasureCache measureCache, AnalyzerIssueCache issueCache, - Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache) { + Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache, + BlockCache blockCache, DuplicationCache duplicationCache) { this.def = def; this.measureCache = measureCache; this.issueCache = issueCache; @@ -71,6 +81,8 @@ public class DefaultSensorContext implements SensorContext { this.activeRules = activeRules; this.issueFilters = issueFilters; this.componentDataCache = componentDataCache; + this.blockCache = blockCache; + this.duplicationCache = duplicationCache; } @Override @@ -175,4 +187,34 @@ public class DefaultSensorContext implements SensorContext { return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache); } + @Override + public TokenBuilder tokenBuilder(InputFile inputFile) { + PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language())); + + return new DefaultTokenBuilder(inputFile, blockCache, blockChunker); + } + + @Override + public DuplicationBuilder duplicationBuilder(InputFile inputFile) { + return new DefaultDuplicationBuilder(inputFile, duplicationCache); + } + + private int getBlockSize(String languageKey) { + int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); + if (blockSize == 0) { + blockSize = getDefaultBlockSize(languageKey); + } + return blockSize; + } + + private static int getDefaultBlockSize(String languageKey) { + if ("cobol".equals(languageKey)) { + return 30; + } else if ("abap".equals(languageKey) || "natur".equals(languageKey)) { + return 20; + } else { + return 10; + } + } + } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java index e13fca37d28..b0a2b3e0af5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java @@ -33,6 +33,8 @@ import org.sonar.api.scan.filesystem.PathResolver; import org.sonar.batch.bootstrap.ExtensionInstaller; import org.sonar.batch.bootstrap.ExtensionMatcher; import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.duplication.BlockCache; +import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.Caches; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.languages.DefaultLanguagesReferential; @@ -107,6 +109,10 @@ public class ProjectScanContainer extends ComponentContainer { ComponentDataCache.class, + // Duplications + BlockCache.class, + DuplicationCache.class, + ScanTaskObservers.class); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java index d437eadb6fd..ea872c4c1bf 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java @@ -53,7 +53,7 @@ public class SensorsExecutor implements BatchComponent { continue; } - LOG.info("Execute analyzer: " + descriptor.name()); + LOG.info("Execute sensor: " + descriptor.name()); executeSensor(context, analyzer); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java new file mode 100644 index 00000000000..ade701fce45 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java @@ -0,0 +1,82 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.duplication; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.index.Caches; +import org.sonar.batch.index.CachesTest; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.fest.assertions.Assertions.assertThat; + +public class DuplicationCacheTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + Caches caches; + + @Before + public void start() throws Exception { + caches = CachesTest.createCacheOnTemp(temp); + caches.start(); + } + + @After + public void stop() { + caches.stop(); + } + + @Test + public void should_add_clone_groups() throws Exception { + DuplicationCache cache = new DuplicationCache(caches); + + DuplicationGroup group1 = new DuplicationGroup(new DuplicationGroup.Block("foo", 1, 2)) + .addDuplicate(new DuplicationGroup.Block("foo", 1, 2)) + .addDuplicate(new DuplicationGroup.Block("foo2", 12, 22)) + .addDuplicate(new DuplicationGroup.Block("foo3", 13, 23)); + + DuplicationGroup group2 = new DuplicationGroup(new DuplicationGroup.Block("2foo", 1, 2)) + .addDuplicate(new DuplicationGroup.Block("2foo", 1, 2)) + .addDuplicate(new DuplicationGroup.Block("2foo2", 12, 22)) + .addDuplicate(new DuplicationGroup.Block("2foo3", 13, 23)); + + assertThat(cache.entries()).hasSize(0); + + cache.put("foo", new ArrayList<DuplicationGroup>(Arrays.asList(group1, group2))); + + assertThat(cache.entries()).hasSize(1); + + ArrayList<DuplicationGroup> entry = cache.byComponent("foo"); + assertThat(entry.get(0).originBlock().resourceKey()).isEqualTo("foo"); + + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java index fe8a1a45a65..dfed349e696 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java @@ -27,6 +27,7 @@ import org.sonar.api.database.model.Snapshot; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.Metric; +import org.sonar.api.measures.MetricFinder; import org.sonar.api.measures.PersistenceMode; import org.sonar.api.measures.RuleMeasure; import org.sonar.api.resources.Directory; @@ -35,10 +36,14 @@ import org.sonar.api.resources.Project; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.RulePriority; +import org.sonar.batch.duplication.DuplicationCache; +import org.sonar.batch.duplication.DuplicationGroup; import org.sonar.batch.scan.measure.MeasureCache; import org.sonar.core.persistence.AbstractDaoTestCase; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -70,6 +75,8 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { public void mockResourcePersister() { snapshotCache = mock(SnapshotCache.class); measureCache = mock(MeasureCache.class); + DuplicationCache duplicationCache = mock(DuplicationCache.class); + when(duplicationCache.entries()).thenReturn(Collections.<Cache.Entry<ArrayList<DuplicationGroup>>>emptyList()); ResourceCache resourceCache = mock(ResourceCache.class); when(snapshotCache.get("foo")).thenReturn(projectSnapshot); when(snapshotCache.get("foo:org/foo")).thenReturn(packageSnapshot); @@ -77,7 +84,7 @@ public class MeasurePersisterTest extends AbstractDaoTestCase { when(resourceCache.get("foo:org/foo/Bar.java")).thenReturn(aFile); when(resourceCache.get("foo:org/foo")).thenReturn(aDirectory); - measurePersister = new MeasurePersister(getMyBatis(), ruleFinder, measureCache, snapshotCache, resourceCache); + measurePersister = new MeasurePersister(getMyBatis(), ruleFinder, measureCache, snapshotCache, resourceCache, duplicationCache, mock(MetricFinder.class)); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java index 674f472d0b5..19d7aea020a 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java @@ -31,8 +31,8 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.api.utils.MessageException; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; import org.sonar.batch.protocol.input.ActiveRule; +import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java index 259b2ffcec0..b9fab7ba450 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java @@ -29,7 +29,7 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; +import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java index 875e7e6d510..f3eebf971e3 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java @@ -29,8 +29,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; import org.sonar.batch.protocol.input.ActiveRule; +import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java index fdf6b15e857..fb3e8d16edd 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java @@ -28,8 +28,8 @@ import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.internal.DefaultInputDir; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; import org.sonar.batch.protocol.input.ActiveRule; +import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java index bb93ae4b7ba..146b89eb316 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java @@ -30,7 +30,7 @@ import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder; import org.sonar.api.measures.CoreMetrics; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; +import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java index c3875c625f3..8772d371ba2 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java @@ -28,7 +28,7 @@ import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.fs.InputFile; import org.sonar.batch.mediumtest.BatchMediumTester; import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; -import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; +import org.sonar.xoo.XooPlugin; import java.io.File; import java.io.IOException; diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java deleted file mode 100644 index 78718da3682..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.batch.mediumtest.xoo.plugin; - -import org.sonar.api.SonarPlugin; -import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; -import org.sonar.batch.mediumtest.xoo.plugin.lang.MeasureSensor; -import org.sonar.batch.mediumtest.xoo.plugin.lang.ScmActivitySensor; -import org.sonar.batch.mediumtest.xoo.plugin.lang.SymbolReferencesSensor; -import org.sonar.batch.mediumtest.xoo.plugin.lang.SyntaxHighlightingSensor; -import org.sonar.batch.mediumtest.xoo.plugin.rule.CreateIssueByInternalKeySensor; -import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssueOnDirPerFileSensor; -import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssuePerLineSensor; - -import java.util.Arrays; -import java.util.List; - -public final class XooPlugin extends SonarPlugin { - - @Override - public List getExtensions() { - return Arrays.asList( - // language - MeasureSensor.class, - ScmActivitySensor.class, - SyntaxHighlightingSensor.class, - SymbolReferencesSensor.class, - Xoo.class, - - // sensors - OneIssuePerLineSensor.class, - OneIssueOnDirPerFileSensor.class, - CreateIssueByInternalKeySensor.class - ); - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java index 7b7eb21ee58..a93bf9bae83 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java @@ -42,6 +42,8 @@ import org.sonar.api.measures.MetricFinder; import org.sonar.api.resources.File; import org.sonar.api.resources.Project; import org.sonar.api.rule.RuleKey; +import org.sonar.batch.duplication.BlockCache; +import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.ComponentDataCache; import static org.fest.assertions.Assertions.assertThat; @@ -71,8 +73,9 @@ public class SensorContextAdapterTest { settings = new Settings(); resourcePerspectives = mock(ResourcePerspectives.class); ComponentDataCache componentDataCache = mock(ComponentDataCache.class); + BlockCache blockCache = mock(BlockCache.class); adaptor = new SensorContextAdaptor(sensorContext, metricFinder, new Project("myProject"), - resourcePerspectives, settings, fs, activeRules, componentDataCache); + resourcePerspectives, settings, fs, activeRules, componentDataCache, blockCache, mock(DuplicationCache.class)); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java index f1bf43f37e3..03dbec79af8 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java @@ -24,6 +24,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile.Status; +import org.sonar.api.batch.fs.InputFile.Type; import org.sonar.api.batch.fs.InputPath; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile; @@ -55,7 +57,18 @@ public class InputPathCacheTest { InputPathCache cache = new InputPathCache(caches); DefaultInputFile fooFile = new DefaultInputFile("src/main/java/Foo.java").setFile(temp.newFile("Foo.java")); cache.put("struts", fooFile); - cache.put("struts-core", new DeprecatedDefaultInputFile("src/main/java/Bar.java").setFile(temp.newFile("Bar.java"))); + cache.put("struts-core", new DeprecatedDefaultInputFile("src/main/java/Bar.java") + .setBasedir(temp.newFolder()) + .setDeprecatedKey("foo") + .setSourceDirAbsolutePath("foo") + .setPathRelativeToSourceDir("foo") + .setLanguage("bla") + .setType(Type.MAIN) + .setStatus(Status.ADDED) + .setHash("xyz") + .setLines(1) + .setKey("foo") + .setFile(temp.newFile("Bar.java"))); assertThat(cache.getFile("struts", "src/main/java/Foo.java").relativePath()) .isEqualTo("src/main/java/Foo.java"); @@ -75,5 +88,4 @@ public class InputPathCacheTest { assertThat(cache.filesByModule("struts-core")).hasSize(1); assertThat(cache.all()).hasSize(1); } - } diff --git a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java index 9d74af46428..8e925d67ca0 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java @@ -33,6 +33,13 @@ public interface ComponentMapper { ComponentDto selectById(long id); + ComponentDto selectRootProjectByKey(String key); + + ComponentDto selectParentModuleByKey(String key); + + /** + * Return direct modules from a project/module + */ List<ComponentDto> findModulesByProject(@Param("projectKey") String projectKey); long countById(long id); diff --git a/sonar-core/src/main/java/org/sonar/core/properties/PropertiesDao.java b/sonar-core/src/main/java/org/sonar/core/properties/PropertiesDao.java index 4357258a1b4..e7a13b39f26 100644 --- a/sonar-core/src/main/java/org/sonar/core/properties/PropertiesDao.java +++ b/sonar-core/src/main/java/org/sonar/core/properties/PropertiesDao.java @@ -96,7 +96,7 @@ public class PropertiesDao implements BatchComponent, ServerComponent, DaoCompon } public List<PropertyDto> selectProjectProperties(String resourceKey, SqlSession session) { - return session.getMapper(PropertiesMapper.class).selectProjectProperties(resourceKey); + return session.getMapper(PropertiesMapper.class).selectProjectProperties(resourceKey); } public List<PropertyDto> selectProjectProperties(String resourceKey) { diff --git a/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java b/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java index 745b21b8c97..cb7ff46ff60 100644 --- a/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java +++ b/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java @@ -24,6 +24,7 @@ public interface SnapshotDataTypes { String SYNTAX_HIGHLIGHTING = "highlight_syntax"; String SYMBOL_HIGHLIGHTING = "symbol"; + String TOKEN = "token"; /** * Key-values [relative path, hash] of all files. Stored on modules. diff --git a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml index f0dc353a03a..b0fd329b070 100644 --- a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml @@ -44,11 +44,34 @@ </where> </select> + <select id="selectRootProjectByKey" parameterType="String" resultType="Component"> + SELECT rootProject.* + FROM projects p + INNER JOIN snapshots s ON s.project_id=p.id AND s.islast=${_true} + INNER JOIN projects rootProject ON rootProject.id=s.root_project_id + <where> + AND p.kee=#{componentKey} + </where> + </select> + + <select id="selectParentModuleByKey" parameterType="String" resultType="Component"> + SELECT <include refid="componentColumns"/> + FROM projects p + INNER JOIN snapshots s ON s.project_id=p.id AND s.islast=${_true} + INNER JOIN snapshots child_snapshots ON child_snapshots.parent_snapshot_id=s.id AND s.islast=${_true} + INNER JOIN projects child ON child.id=child_snapshots.project_id AND child.enabled=${_true} AND child.kee=#{key} + <where> + AND p.enabled=${_true} + AND p.scope='PRJ' + </where> + </select> + <select id="findModulesByProject" parameterType="String" resultType="Component"> SELECT <include refid="componentColumns"/> FROM projects p - INNER JOIN projects root ON root.id=p.root_id AND root.enabled=${_true} AND root.kee=#{projectKey} INNER JOIN snapshots s ON s.project_id=p.id AND s.islast=${_true} + INNER JOIN snapshots parent_snapshots ON parent_snapshots.id=s.parent_snapshot_id AND s.islast=${_true} + INNER JOIN projects parent ON parent.id=parent_snapshots.project_id AND parent.enabled=${_true} AND parent.kee=#{projectKey} <where> AND p.enabled=${_true} AND p.scope='PRJ' diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 892226ed05c..c887b3526ec 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1741,6 +1741,7 @@ coding_rules.deactivate_in_all_quality_profiles=Deactivate In All {0} Profiles coding_rules.found=Found coding_rules.inherits="{0}" inherits from "{1}" coding_rules.key=Key: +coding_rules.manual_rule=Manual Rule coding_rules.new_search=New Search coding_rules.no_results=No Coding Rules coding_rules.no_tags=No tags @@ -1798,6 +1799,7 @@ coding_rules.filters.template.is_not_template=Hide Templates coding_rules.facets.languages=Languages coding_rules.facets.tags=Tags coding_rules.facets.repositories=Repositories +coding_rules.facets.top=Top {0} coding_rules.sort.creation_date=Creation Date coding_rules.sort.name=Name @@ -2536,10 +2538,10 @@ metric.new_technical_debt.name=Technical Debt on new code metric.new_technical_debt.description=Technical Debt on new code metric.sqale_rating.name=SQALE Rating -metric.sqale_rating.description=Density of technical debt computed by dividing the technical debt by the estimated effort to develop from scratch an application. +metric.sqale_rating.description=Rating of the technical debt ratio based on the SQALE Governance Model. metric.sqale_debt_ratio.name=Technical Debt Ratio -metric.sqale_debt_ratio.description=Ratio of the technical debt compared to what it would cost to develop the whole source code from scratch. +metric.sqale_debt_ratio.description=Ratio of the actual technical debt compared to the estimated cost to develop the whole source code from scratch. #------------------------------------------------------------------------------ diff --git a/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java b/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java index ffd90b34de2..34c5d6bbdbd 100644 --- a/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java +++ b/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java @@ -19,9 +19,6 @@ */ package org.sonar.api.batch; -import org.sonar.api.batch.sensor.Sensor; -import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; - import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; @@ -29,6 +26,8 @@ import org.apache.commons.lang.ClassUtils; import org.sonar.api.BatchExtension; import org.sonar.api.batch.maven.DependsUponMavenPlugin; import org.sonar.api.batch.maven.MavenPluginHandler; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.utils.AnnotationUtils; diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java b/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java index aab2802c27b..a3deacff154 100644 --- a/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java +++ b/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java @@ -60,7 +60,7 @@ public final class ByteArray { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), - (byte) value }; + (byte) value}; } public ByteArray(int value) { @@ -68,7 +68,7 @@ public final class ByteArray { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), - (byte) value }; + (byte) value}; } public ByteArray(int[] intArray) { @@ -79,6 +79,10 @@ public final class ByteArray { this.bytes = bb.array(); } + public byte[] getBytes() { + return bytes; + } + public int[] toIntArray() { // Pad the size to multiple of 4 int size = (bytes.length / 4) + (bytes.length % 4 == 0 ? 0 : 1); diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java b/sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java new file mode 100644 index 00000000000..7b43988efc2 --- /dev/null +++ b/sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java @@ -0,0 +1,45 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.duplications.block; + +import java.util.List; + +/** + * Represents all blocks in a file. + */ +public final class FileBlocks { + + private final String resourceId; + private final List<Block> blocks; + + public FileBlocks(String resourceId, List<Block> blocks) { + this.resourceId = resourceId; + this.blocks = blocks; + } + + public String resourceId() { + return resourceId; + } + + public List<Block> blocks() { + return blocks; + } + +} diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java b/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java index 7c9ad2a8278..01b7273da1c 100644 --- a/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java +++ b/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java @@ -20,8 +20,10 @@ package org.sonar.duplications.index; import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; import java.util.List; /** @@ -32,6 +34,7 @@ public class CloneGroup { private final ClonePart originPart; private final int cloneLength; private final List<ClonePart> parts; + private int length; /** * Cache for hash code. @@ -52,7 +55,7 @@ public class CloneGroup { private ClonePart origin; private int length; private int lengthInUnits; - private List<ClonePart> parts; + private List<ClonePart> parts = new ArrayList<ClonePart>(); public Builder setLength(int length) { this.length = length; @@ -69,6 +72,12 @@ public class CloneGroup { return this; } + public Builder addPart(ClonePart part) { + Preconditions.checkNotNull(part); + this.parts.add(part); + return this; + } + public Builder setLengthInUnits(int length) { this.lengthInUnits = length; return this; @@ -90,8 +99,6 @@ public class CloneGroup { return originPart; } - private int length; - /** * Length of duplication measured in original units, e.g. for token-based detection - in tokens. * diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java index 1c7c0ee4437..b0ae419332a 100644 --- a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java +++ b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java @@ -23,7 +23,7 @@ import com.google.common.collect.Lists; import org.sonar.duplications.block.Block; import org.sonar.duplications.block.ByteArray; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; /** @@ -48,7 +48,10 @@ public class PmdBlockChunker { this.power = pow; } - public List<Block> chunk(String resourceId, List<TokensLine> fragments) { + /** + * @return ArrayList as we need a serializable object + */ + public ArrayList<Block> chunk(String resourceId, List<TokensLine> fragments) { List<TokensLine> filtered = Lists.newArrayList(); int i = 0; while (i < fragments.size()) { @@ -66,10 +69,10 @@ public class PmdBlockChunker { fragments = filtered; if (fragments.size() < blockSize) { - return Collections.emptyList(); + return Lists.newArrayList(); } TokensLine[] fragmentsArr = fragments.toArray(new TokensLine[fragments.size()]); - List<Block> blocks = Lists.newArrayListWithCapacity(fragmentsArr.length - blockSize + 1); + ArrayList<Block> blocks = Lists.newArrayListWithCapacity(fragmentsArr.length - blockSize + 1); long hash = 0; int first = 0; int last = 0; @@ -84,11 +87,11 @@ public class PmdBlockChunker { hash = hash * PRIME_BASE + lastFragment.getHashCode(); // create block Block block = blockBuilder - .setBlockHash(new ByteArray(hash)) - .setIndexInFile(first) - .setLines(firstFragment.getStartLine(), lastFragment.getEndLine()) - .setUnit(firstFragment.getStartUnit(), lastFragment.getEndUnit()) - .build(); + .setBlockHash(new ByteArray(hash)) + .setIndexInFile(first) + .setLines(firstFragment.getStartLine(), lastFragment.getEndLine()) + .setUnit(firstFragment.getStartUnit(), lastFragment.getEndUnit()) + .build(); blocks.add(block); // remove first statement from hash hash -= power * firstFragment.getHashCode(); diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java index e8dcc4bad76..80fc8691717 100644 --- a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java +++ b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java @@ -69,7 +69,7 @@ public class TokenizerBridge { * We expect that implementation of {@link Tokenizer} is correct: * tokens ordered by occurrence in source code and last token is EOF. */ - private static List<TokensLine> convert(List<TokenEntry> tokens) { + public static List<TokensLine> convert(List<TokenEntry> tokens) { ImmutableList.Builder<TokensLine> result = ImmutableList.builder(); StringBuilder sb = new StringBuilder(); int startLine = Integer.MIN_VALUE; diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java index 9c07b381240..d8f5a304af9 100644 --- a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java +++ b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java @@ -25,7 +25,7 @@ import org.sonar.duplications.CodeFragment; /** * Immutable code fragment, which formed from tokens of one line. */ -class TokensLine implements CodeFragment { +public class TokensLine implements CodeFragment { private final String value; @@ -35,7 +35,6 @@ class TokensLine implements CodeFragment { private final int startUnit; private final int endUnit; - public TokensLine(int startUnit, int endUnit, int startLine, String value) { Preconditions.checkArgument(startLine > 0); // TODO do we have requirements for length and hashcode ? diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index aad1de5ea03..b73e0a0ecee 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -344,10 +344,10 @@ public interface CoreProperties { /** * @since 2.11 */ - String CPD_CROSS_RPOJECT = "sonar.cpd.cross_project"; + String CPD_CROSS_PROJECT = "sonar.cpd.cross_project"; /** - * @see #CPD_CROSS_RPOJECT + * @see #CPD_CROSS_PROJECT * @since 2.11 */ boolean CPD_CROSS_RPOJECT_DEFAULT_VALUE = false; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java index 156e140fa57..7c82aa501d0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java @@ -50,8 +50,9 @@ public class DeprecatedDefaultInputFile extends DefaultInputFile implements org. return new File(basedir); } - public void setBasedir(File basedir) { + public DeprecatedDefaultInputFile setBasedir(File basedir) { this.basedir = PathUtils.sanitize(basedir.getAbsolutePath()); + return this; } /** diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index 787d2fddb3d..647cab3f3f9 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -24,6 +24,8 @@ import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.duplication.DuplicationBuilder; +import org.sonar.api.batch.sensor.duplication.TokenBuilder; import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; @@ -126,4 +128,19 @@ public interface SensorContext { */ SymbolTableBuilder symbolTableBuilder(InputFile inputFile); + // ------------ DUPLICATIONS ------------ + + /** + * Builder to define tokens in a file. Tokens are used to compute duplication by the core. + * @since 4.5 + */ + TokenBuilder tokenBuilder(InputFile inputFile); + + /** + * Builder to manually define duplications in a file. When duplication are manually computed then + * no need to use {@link #tokenBuilder(InputFile)}. + * @since 4.5 + */ + DuplicationBuilder duplicationBuilder(InputFile inputFile); + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java new file mode 100644 index 00000000000..2f37220b1a3 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java @@ -0,0 +1,38 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.sensor.duplication; + +import org.sonar.api.batch.fs.InputFile; + +/** + * This builder is used to declare duplications on files of the project. + * @since 4.5 + */ +public interface DuplicationBuilder { + + DuplicationBuilder originBlock(int startLine, int endLine); + + DuplicationBuilder isDuplicatedBy(InputFile sameOrOtherFile, int startLine, int endLine); + + /** + * Call this method only once when your are done with defining all duplicates of origin block. + */ + void done(); +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java new file mode 100644 index 00000000000..f4de9fffc8c --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java @@ -0,0 +1,40 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.sensor.duplication; + +/** + * This builder is used to define token on files. Tokens are later used to compute duplication. + * Tokens should be declared in sequential order. + * @since 4.5 + */ +public interface TokenBuilder { + + /** + * Call this method to register a new token. + * @param line Line number of the token. Line starts at 1. + * @param image Text of the token. + */ + TokenBuilder addToken(int line, String image); + + /** + * Call this method only once when your are done with defining tokens of the file. + */ + void done(); +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java new file mode 100644 index 00000000000..4973316830a --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.duplication; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java index 7a86f7b145c..870a8519271 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java @@ -343,6 +343,7 @@ public class Measure<G extends Serializable> implements Serializable { /** * @return the data field of the measure */ + @CheckForNull public String getData() { return data; } |