require 'active_support/core_ext/string/inflections' require 'uri' begin require 'scrub_rb' rescue LoadError # Temporary patch as the scrub_rb gem is not installed to the central Ruby installation. # This means that when running Origen outside of an application this functionality is not # available (which is fine), within an application the gem will be loaded correctly by # Bundler and the gem will require quite happily. end class String def to_dec if is_verilog_number? verilog_to_dec elsif match(/^0[x,o,d,b]\S+/) _to_dec(self) else to_i end end def escape_underscores(smartly = false) if smartly # Need to make sub-string ranges where the URLs are located urls = URI.extract(self, %w(http https ssh)) url_matches = [].tap do |new_ary| urls.each do |url| scan(/#{Regexp.escape(url)}/) do |c| indices = ($LAST_MATCH_INFO.offset(0).first..($LAST_MATCH_INFO.offset(0).first + c.size - 1)) new_ary << [indices, c] end end end # This order is because a better single regex is not yet found italic_regex = [/^(\_\w+\_)\s+/, /(\_\w+\_)$/, /\s*(\_\w+\_)\s+/] italic_matches = [].tap do |new_ary| italic_regex.each_with_index do |regex, i| scan(regex) do |c| offset = c.first.size - 1 if i == 0 indices = (0..offset) elsif i == 1 indices = (size - 1 - offset..size - 1) else indices = ($LAST_MATCH_INFO.offset(0).first..($LAST_MATCH_INFO.offset(0).first + c.first.size - 1)) end new_ary << [indices, c] end end end # Make sure there are no italic matches within the URL ranges filtered_italic_matches = [].tap do |new_ary| url_matches.each do |url_ary| url_range = url_ary.first italic_matches.each do |italic_ary| italic_range = italic_ary.first # Ruby 2.6 has the Range#cover method but until then unless ranges_overlap?(url_range, italic_range) new_ary << italic_ary unless new_ary.include?(italic_ary) end end end end italic_ranges = [].tap do |new_ary| filtered_italic_ranges = filtered_italic_matches.map(&:first) italic_range_mins = filtered_italic_matches.map(&:first).map(&:min).sort italic_range_mins.each_with_index do |range_min, i| filtered_italic_ranges.each do |r| new_ary << r if r.min == range_min end end end str = dup inc = 0 italic_ranges.each do |r| str[r.first + inc] = '\_' inc += 1 str[r.last + inc] = '\_' inc += 1 end str else gsub('_', '\_') end end alias_method :escape_underscore, :escape_underscores def match_all(regex) match_str = self matches = [] while match_str.length > 0 md = match_str.match(regex) break unless md matches << md match_str = md.post_match end matches end def camel_case Origen.deprecate "String#camel_case! is deprecated, use String#camelcase instead, or if you want to get rid of spaces: my_string.gsub(' ', '_').camelcase" gsub(/\s+/, '_').split('_').map(&:capitalize).join end def pad_leading_zeros(width) str = self (0..(width - size) - 1).each { str = '0' + str } str end def to_lines(length) lines = [] line = [] len = 0 split(/\s+/).each do |word| if (len + word.length) > length lines << line.join(' ') line = [] len = 0 end line << word len += word.length + 1 # For the trailing space end lines << line.join(' ') unless line.empty? lines end # Sanitizes the string for conversion to a symbol and returns a lower # cased symbol version of the string def symbolize orig_had_leading_underscore = match(/^\_/) ? true : false orig_had_trailing_underscore = match(/\_$/) ? true : false new_str = gsub(/(\?|\!|\-|\/|\\|\n|\s|\(|\)|\.|\[|\]|-|{|})/, '_').downcase # Get rid of adjacent underscores new_str.match(/\_\_/) ? new_str = new_str.squeeze('_') : new_str new_str.chomp!('_') unless orig_had_trailing_underscore unless orig_had_leading_underscore new_str = new_str[1..-1] if new_str.match(/^\_/) end @@symbolize ||= {} @@symbolize[self] ||= new_str.to_sym end # acronyms def to_snakecase! Origen.deprecate 'String#to_snakecase! is deprecated, use String#underscore instead since it is aware of FSL acronyms' gsub!(/\s+/, '') g = gsub!(/(.)([A-Z])/, '\1_\2'); d = downcase! g || d end alias_method :snakecase!, :to_snakecase! def to_snakecase Origen.deprecate 'String#to_snakecase is deprecated, use String#underscore instead since it is aware of FSL acronyms' dup.tap(&:to_snakecase!) end alias_method :snakecase, :to_snakecase def squeeze_lines split(/\n+/).join(' ').squeeze(' ') end # Attempt to convert a String to a boolean answer def to_bool if self == true || self =~ (/^(true|t|yes|y|1)$/i) true elsif self == false || empty? || self =~ (/^(false|f|no|n|0)$/i) false end end # Check if a String is a numeric def is_numeric? return true if self =~ /\A\d+\Z/ true if Float(self) rescue false # rubocop:disable Style/RescueModifier end alias_method :numeric?, :is_numeric? # Convert the String to a Numeric (Float or Integer) def to_numeric if numeric? if to_i == to_f # rubocop:disable Lint/FloatComparison to_i else to_f end else fail "'#{self}' cannot be converted to a Numeric, exiting..." end end alias_method :to_number, :to_numeric # Capitalize every word def titleize(options = {}) options = { keep_specials: false }.update(options) if options[:keep_specials] split.map(&:capitalize).join(' ') else split(/ |\_|\-/).map(&:capitalize).join(' ') end end def is_verilog_number? case self when /^[b,o,d,h]\S+$/, /^\d+\'[b,o,d,h]\S+$/, /^\d+\'s[b,o,d,h]\S+$/ true else false end end # Boolean if the string is uppercase # Will not work with odd character sets def is_upcase? self == upcase end alias_method :is_uppercase?, :is_upcase? # Boolean if the string is uppercase # Will not work with odd character sets def is_downcase? self == downcase end alias_method :is_lowercase?, :is_downcase? # Convert Excel/Spreadsheet column to integer def excel_col_index str = split('').map(&:upcase).join('') offset = 'A'.ord - 1 str.chars.inject(0) { |x, c| x * 26 + c.ord - offset } end alias_method :xls_col_index, :excel_col_index alias_method :xlsx_col_index, :excel_col_index alias_method :spreadsheet_col_index, :excel_col_index private def ranges_overlap?(r1, r2) !(r1.first > r2.last || r1.last < r2.first) end # Convert a verilog number string to an integer def verilog_to_dec verilog_hash = parse_verilog_number bit_str = verilog_to_bits msb_size_bit = bit_str.size - verilog_hash[:size] if verilog_hash[:signed] == true if bit_str[msb_size_bit] == '1' _to_dec("0b#{bit_str}") * -1 else _to_dec("0b#{bit_str}") end else _to_dec("0b#{bit_str}") end end # Convert a verilog number string to a bit string def verilog_to_bits verilog_hash = parse_verilog_number if [verilog_hash[:radix], verilog_hash[:value]].include?(nil) Origen.log.error("The string '#{self}' does not appear to be valid Verilog number notation!") fail end value_bit_string = create_bit_string_from_verilog(verilog_hash[:value], verilog_hash[:radix]) audit_verilog_value(value_bit_string, verilog_hash[:radix], verilog_hash[:size], verilog_hash[:signed]) end def _to_dec(str) if str =~ /^0x(.*)/i Regexp.last_match[1].to_i(16) elsif str =~ /0d(.*)/i Regexp.last_match[1].to_i(10) elsif str =~ /0o(.*)/i Regexp.last_match[1].to_i(8) elsif str =~ /0b(.*)/ Regexp.last_match[1].to_i(2) end end def parse_verilog_number str = nil verilog_hash = {}.tap do |parse_hash| [:size, :radix, :value].each do |attr| parse_hash[attr] = nil end end verilog_hash[:signed] = false if match(/\_/) str = delete('_') else str = self end str.downcase! case str when /^[0,1]+$/ # Just a value verilog_hash[:size], verilog_hash[:radix], verilog_hash[:value] = 32, 'b', self when /^[b,o,d,h]\S+$/ # A value and a radix _m, verilog_hash[:radix], verilog_hash[:value] = /^([b,o,d,h])(\S+)$/.match(str).to_a verilog_hash[:size] = calc_verilog_value_bit_size(verilog_hash[:value], verilog_hash[:radix]) when /^\d+\'[b,o,d,h]\S+$/ # A value, a radix, and a size _m, verilog_hash[:size], verilog_hash[:radix], verilog_hash[:value] = /^(\d+)\'([b,o,d,h])(\S+)$/.match(str).to_a when /^\d+\'s[b,o,d,h]\S+$/ # A signed value, a radix, and a size _m, verilog_hash[:size], verilog_hash[:radix], verilog_hash[:value] = /^(\d+)\'s([b,o,d,h])(\S+)$/.match(str).to_a verilog_hash[:signed] = true else Origen.log.error("The string '#{self}' does not appear to be valid Verilog number notation!") fail end verilog_hash[:size] = verilog_hash[:size].to_i if verilog_hash[:size].is_a?(String) verilog_hash end def calc_verilog_value_bit_size(val, radix) create_bit_string_from_verilog(val, radix).size end def create_bit_string_from_verilog(val, radix) bit_str = '' case radix when 'b' return val when 'o', 'd' bit_str = "0#{radix}#{val}".to_dec.to_bin when 'h' bit_str = "0x#{val}".to_dec.to_bin end 2.times { bit_str.slice!(0) } bit_str end def audit_verilog_value(bit_str, radix, size, signed) size_diff = bit_str.size - size if size_diff > 0 Origen.log.warn("Truncating Verilog number '#{self}' by #{size_diff} MSBs!") size_diff.times { bit_str.slice!(0) } elsif size_diff < 0 bit_str = '0' * size_diff.abs + bit_str end bit_str end end